boost thread
1. thread
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } void thread() { for (int i = 0; i < 5; ++i) { wait(1); std::cout << i << std::endl; } } int main() { boost::thread t{thread}; t.join();
boost::scoped_thread<> t2(boost::thread(thread));
return 0;
}
The name of the function that the new thread should execute is passed to the constructor of boost::thread. Once the variable t is created, the function thread() starts immediately executing in its own thread. At this point, thread() executes concurrently with the main() function.
To keep the program from terminating, join() is called on the newly created thread. join() blocks the current thread until the thread for which join() was called has terminated. This causes main() to wait until thread() returns.
A particular thread can be accessed using a variable – t in this example – to wait for its termination. However, the thread will continue to execute even if t goes out of scope and is destroyed.
The constructor of boost::scoped_thread expects an object of type boost::thread. In the destructor of boost::scoped_thread an action has access to that object. By default, boost::scoped_thread uses an action that calls join() on the thread.
2. interruption point with boost::this_thread::sleep_for()
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } void thread() {
//boost::this_thread::disable_interruption no_interruption; try { for (int i = 0; i < 5; ++i) { wait(1); std::cout << i << std::endl; } } catch (boost::thread_interrupted&) {} } int main() { boost::thread t{thread}; wait(3); t.interrupt(); t.join(); return 0; }
Calling interrupt() on a thread object interrupts the corrsponding thread. In this context, interrupt means that an exception of type boost::thread_interrupted is thrown in the thread. However, this only happens when the thread reaches an interruption point.
Boost.Thread defines a series of interuption points such as the sleep_for() function.
The class boost::this_thread::disable_interruption prevents a thread from being interrupted. If you instantiate boost::this_thread::disable_interruption, interruption points in a thread will be disabled as long as the object exists.
Synchronizing Threads
3. mutex
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds(seconds)); } boost::mutex mutex; void thread() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); mutex.lock(); std::cout << "Thread " << get_id() << ": " << i << std::endl; mutex.unlock(); } } int main() { boost::thread t1{thread}; boost::thread t2{thread}; t1.join(); t2.join(); return 0; }
Multithreaded programs use mutexes for synchronization. Boost.Thread provides different mutex classes with boost::mutex
being the simplest. The basic principle of a mutex is to prevent other threads from taking ownership while a particular thread owns the mutex. Once released, a different thread can take ownership. This causes threads to wait until the thread that owns the mutex has finished processing and releases its ownership of the mutex.
The thread() function takes ownership of this object by calling lock(). This is done right before the function writes to the standard output stream. Once a message has been written, ownership is released by calling unlock().
4. boost::lock_guard with guaranteed mutex release
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::mutex mutex; void thread() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::lock_guard<boost::mutex> lock{mutex}; std::cout << "Thread " << get_id() << ": " << i << std::endl; } } int main() { boost::thread t1{thread}; boost::thread t2{thread}; t1.join(); t2.join(); return 0; }
boost::lock_guard automatically calls lock() and unlock() in its constructor and its destructor. The class boost::lock_guard is an example of the RAII idiom to make sure resources are released when they are no longer needed.
5. the versatile lock boost::unique_lock
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::timed_mutex mutex; void thread1() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::unique_lock<boost::timed_mutex> lock{mutex}; std::cout << "Thread " << get_id() << ": " << i << std::endl; boost::timed_mutex *m = lock.release(); m->unlock(); } } void thread2() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::unique_lock<boost::timed_mutex> lock{mutex, boost::try_to_lock}; if (lock.owns_lock() || lock.try_lock_for(boost::chrono::seconds{1})) { std::cout << "Thread " << get_id() << ": " << i << std::endl; } } } int main() { boost::thread t1{thread1}; boost::thread t2{thread2}; t1.join(); t2.join(); return 0; }
thread1() passes the variable mutex to the constructor of boost::unique_lock, which makes boost::unique_lock try to lock the mutex. In this case boost::unique_lock behaves no differently than boost::lock_guard. The constructor of boost::unique_lock calls lock on the mutex.
However, the destructor of boost::unique_lock doesn't release the mutex in thread1(). In threa1() release is called on the clock, which decouples the mutex from the lock. By default, the destructor of boost::unique_lock releases a mutex, like the destructor of boost::lock_guard - but not if the mutex is decoupled. That's why there is an explicit to unlock() in thread1().
try_to_lock() tries to lock the mutex. If the mutex is owned by another thread, the try fails. owns_lock() lets you detect whether boost::unique_lock was able to lock a mutex. try_lock_for() also tries to lock a mutex, but it waits for the mutex for a specified period of time before failing.
The reason why uses boost::timed_mutex is that this mutex is the only one that provides the member function try_lock_for(). boost::mutex() provides only the member functions lock() and try_lock().
6. shared locks with boost::shared_lock
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::shared_mutex mutex; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::shared_mutex> lock{mutex}; random_numbers.push_back(std::rand()); lock.unlock(); wait(1); } } void print() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock{mutex}; std::cout << random_numbers.back() << std::endl; } } int sum = 0; void count() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock{mutex}; sum += random_numbers.back(); } } int main() { boost::thread t1{fill}, t2{print}, t3{count}; t1.join(); t2.join(); t3.join(); std::cout << "Sum: " << sum << std::endl; return 0; }
Non-exclusive locks of type boost::shared_lock can be used if threads only need read-only access to a specific resource. A thread modifying the resource needs write access and thus requires an exclusive lock.
7. condition variables with boost::condition_variable_any
#include <boost/thread.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> boost::mutex mutex; boost::condition_variable_any cond; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock{mutex}; random_numbers.push_back(std::rand()); cond.notify_all(); cond.wait(mutex); } } void print() { std::size_t next_size = 1; for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock{mutex}; while (random_numbers.size() != next_size) cond.wait(mutex); std::cout << random_numbers.back() << std::endl; ++next_size; cond.notify_all(); } } int main() { boost::thread t1{fill}; boost::thread t2{print}; t1.join(); t2.join(); return 0; }
Calling notify_all() will wake up every thread that has been waiting for this notification with wait(). When the thread is woken up by a call to notify_all(), it tries to acquire the mutex, which will only succeed after the mutex has been successfully released.
The trick here is that calling wait() also releases the mutex which was passed as parameter. After calling notify_all()
, the fill()
function releases the mutex by calling wait()
. It then blocks and waits for some other thread to call notify_all()
, which happens in the print()
function once the random number has been written to the standard output stream.
Thread Local Storage
8. synchronizing mutiple threads with TLS
#include <boost/thread.hpp> #include <iostream> boost::mutex mutex; void init() { static boost::thread_specific_ptr<bool> tls; if (!tls.get()) { tls.reset(new bool{true}); boost::lock_guard<boost::mutex> lock{mutex}; std::cout << "done" << std::endl; } } void thread() { init(); init(); } int main() { boost::thread t[3]; for (int i = 0; i < 3; ++i) t[i] = boost::thread{thread}; for (int i = 0; i < 3; ++i) t[i].join(); return 0; }
The value stored by tls is only visible and available to the corresponding thread. Once a variable of type boost::thread_specific_ptr is created, it can be set. This variable expects the address of a variable of type bool instead of the variable itself. Using the reset() member function, the address can be stored in tls.
Because boost::thread_specific_ptr
stores an address, this class behaves like a pointer. For example, it provides the member functions operator*
and operator->
, which work as you would expect them to work with pointers.
Because a TLS variable is used, each thread uses its own variable tls. When the first thread initializes tls with a pointer to a dynamically allocated boolean variable, the tls variables in the second and third thread are still uninitialized. Since TLS variables are global per thread, not global per process, using tls in one thread does not change the variable in any other thread.
Futures and Promises
Boost.Thread provides boost::future to define a future. The class defines a member function get() to get the value. get() is blocking function that may have to wait another thread. To set a value in a future, you need to use a promise. Boost.Thread provides the class boost::promise, which has a member function set_value(). You can get a future from a promise with get_future().
9. boost::future and boost::promise
#define BOOST_THREAD_PROVIDES_FUTURE #include <boost/thread.hpp> #include <boost/thread/future.hpp> #include <functional> #include <iostream> void accumulate(boost::promise<int> &p) { int sum = 0; for (int i = 0; i < 5; ++i) sum += i; p.set_value(sum); } int main() { boost::promise<int> p; boost::future<int> f = p.get_future(); boost::thread t{accumulate, std::ref(p)}; std::cout << f.get() << std::endl; return 0; }
The future f is received from the promise p. A reference to the promise is then passed to the thread t which executes the accumulate() function. The future f and the promise p are linked. When get() is called on the future, the value stored in the promise with set_value() is returned.
10. boost::packaged_task
#define BOOST_THREAD_PROVIDES_FUTURE #include <boost/thread.hpp> #include <boost/thread/future.hpp> #include <utility> #include <iostream> int accumulate() { int sum = 0; for (int i = 0; i < 5; ++i) sum += i; return sum; } int main() { boost::packaged_task<int> task{accumulate}; boost::future<int> f = task.get_future(); boost::thread t{std::move(task)}; std::cout << f.get() << std::endl; return 0; }
The constructor of boost::packaged_task expects as a parameter the function that will be executed in a thread, but boost::packaged_task doesn't start a thread itself. An object of type boost::packaged_task has to be passed to the constructor of boost::thread for the function to be executed in a new thread.
The advantage of boost::packaged_task
is that it stores the return value of a function in a future. You don’t need to adapt a function to store its value in a future. boost::packaged_task
can be seen as an adapter which can store the return value of any function in a future.
11. boost::async()
#define BOOST_THREAD_PROVIDES_FUTURE #include <boost/thread.hpp> #include <boost/thread/future.hpp> #include <iostream> int accumulate() { int sum = 0; for (int i = 0; i < 5; ++i) sum += i; return sum; } int main() { boost::future<int> f = boost::async(accumulate); std::cout << f.get() << std::endl; return 0; }
The function boost::async() unifies boost::packaged_task and boost::thread. It starts accumulate() in a new thread and return a future.