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). 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 anthony@justsoftwaresolutions.co.uk.