Protecting Shared Data
The changes to Boost Threads aren't just in the thread management interface: The thread synchronization primitives have been changed, too. Probably the most obvious change is that the lock()
and unlock()
member functions are now public, and the scoped_lock
types are just specializations of the new boost::unique_lock<>
template. Because this is a template, it can be used with anything that implements the Lockable conceptwhich includes instantiations of boost::unique_lock<>
! Though you wouldn't generally write boost::unique_lock <boost::unique_lock<some_mutex>>
, it is helpful in those cases where you wish your code to work with any type of mutex, as users can pass in a lock
object instead. For example, it is used as part of the implementation of the new lock()
functions for locking more than one mutex together.
boost::unique_lock<>
is also movable, so locks can be transferred between objects (and thus between scopes). This makes it much easier to ensure that the mutex is correctly unlocked even when the unlock()
is in a different function to the lock().
The flexibility with unique_lock
(and the old scoped_lock
types) comes at a pricethe implementation must hold a flag to indicate whether or not the lock
object owns the lock on the mutex, and this must be checked in the destructor. For those cases where you only need strict scoped locking, the boost::lock_guard<>
template provides a smaller, lighter option. It locks the mutex in the constructor and unlocks it in the destructor, cannot be moved and cannot be unlocked without destroying it.
A multiple-reader/single-writer mutex has also made a comeback in the form of boost::shared_mutex
based on Howard Hinnant's proposal in N2094 (see www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2094.html). This has a brand new interface, which features the boost::shared_lock<>
template for acquiring shared ownership. It also provides the necessary lock()
and unlock()
functions, so boost::unique_lock< boost::shared_mutex>
can be used for acquiring exclusive ownership.
Locking Multiple Mutexes Without Deadlock
One of the biggest issues with writing multithreaded code is the possibility of deadlock. One case where it is particularly problematic is where you must acquire locks on more than one mutex in order to perform an operation. Unless this is done carefully, different threads could attempt to acquire the locks in different orders, and thus each end up holding some locks whilst waiting for the others: deadlock! If you can structure your code so that the locks can be acquired together, the 1.36.0 release of Boost provides a solution to that in the form of the boost::lock()
and boost::try_lock()
functions.
These function templates let you specify up to five individual Lockable objects to lock, or an iterator range. They will then attempt to lock all the objects. lock()
will block until it can acquire all the locks, but will not hold any locks while it is blocking. The algorithm used ensures that lock()
will not deadlock with any other thread that attempts to acquire the same set of locks, in whatever order. try_lock()
will either acquire all or none of the set; the return value indicates the success or failure of the call, and which lock couldn't be acquired in the case of failure.
One case where they can be used is for the comparison of two objects that can also be modified by other threads, and so the member data must be protected by a mutex. If two threads try and compare the same two objects, they need to ensure their mutexes are locked in the same order to avoid deadlock. This is where boost::lock
comes in: It enables both to be locked without the user having to worry about the order. In the following example, boost::lock
is used to lock the mutexes, then ownership of those locks is adopted by some scoped_lock
objects to ensure the locks are correctly released when the function exits:
struct my_class { mutable boost::mutex m; int x,y; }; bool operator<(my_class const& lhs,my_class const& rhs) { boost::lock(lhs.m,rhs.m); boost::mutex::scoped_lock lk1(lhs.m,boost::adopt_lock); boost::mutex::scoped_lock lk2(rhs.m,boost::adopt_lock); return (lhs.x<rhs.x) || ((lhs.x==rhs.x) && (lhs.y<rhs.y)); }
Enhanced Condition Variables
With Boost 1.35.0, the condition variable implementation was given a revamp to support interruption, and a new type of condition variable was added: boost::condition_variable_any
. Whereas boost::condition_variable
only works with boost::unique_lock<boost::mutex>
(or its synonym, boost::mutex::scoped_lock
), the wait functions for boost::condition_variable_any
can be used with any mutex type. This means that you can now use boost::timed_mutex
with boost::condition_variable_any
, and boost::recursive_mutex
(though care needs to be taken to ensure it isn't recursively locked, as only one level of unlocking will be done by wait()
). Even boost::shared_lock<boost::shared_mutex>
can be used, or a user-provided mutex type.
boost::condition_variable_any cv; boost::shared_mutex sm; bool update_ready(); void process_shared_data(); void wait_for_data() { boost::shared_lock<boost::shared_mutex> lk(sm); cv.wait(lk,update_ready); process_shared_data(); }
Date-Time Integration
The final change since Boost 1.34 is the integration with Boost.DateTime
. This has been a long time coming, and means that finally, you can specify timeouts as boost::posix_time::milliseconds(50)
(for example) rather than doing a dance with boost::xtime
. Absolute timeouts are still supported through the new boost::system_time
typedef (and xtime
, for backwards compatibility), but all thread functions that take a timeout now accept both absolute and relative timeouts.
Summary
The Boost Thread library has undergone some major changes in 2008. These changes help pave the way for the upcoming C++0x Standard, and make it easier for users to write robust multithreaded programs. This isn't the end of the line though: As the C++0x working draft evolves, the Boost Thread library will evolve with it, both to incorporate the changes and to provide a proving ground for new ideas.
Anthony is founder and managing director of Just Software Solutions. He is also maintainer of the Boost Thread library and author of the upcoming C++ Concurrency in Action: Practical Multithreading. He can be contacted at [email protected].