What's New in Boost Threads?

The Boost.Thread library, which enables the use of multiple threads of execution with shared data in portable C++ code, has undergone some major changes.


October 24, 2008
URL:http://www.drdobbs.com/tools/whats-new-in-boost-threads/211600441

Having somewhat languished unchanged for a couple of years, the Boost Thread library (boost.org) finally got some of the big changes it deserved with a major rewrite for the April 2008 release of Boost 1.35.0. Hot on the heels of 1.35.0 is the Boost 1.36.0 release in August 2008, which includes even more improvements to the Thread library. Certainly, a lot has changed since William Kempf wrote The Boost.Threads Library in 2002.

These releases have included some significant changes that either bring the library more in line with the proposals for the new C++0x thread library, or provide additional features as extensions. In this article, I present an overview of the new features and changes in the 1.35.0 and 1.36.0 releases from the user's point of view.

Launching Threads

As in prior releases of Boost, threads are started by constructing instances of boost::thread. However, there are a few notable changes to the boost::thread class.

One of the most significant changes is that boost::thread objects are now moveable. Taking advantage of the new rvalue-reference facility from C++0x where available (recent versions of GCC, for instance), and with a library-based move facility for other compilers, ownership of a specific thread of execution can be transferred between instances of boost::thread. This means that you can return objects of type boost::thread from a function, and store them in arrays or move-aware containers. This lets you encapsulate the thread creation process in a function without having to dynamically allocate the thread object.


boost::thread create_thread()
{
    void some_function();
    boost::thread t(some_function);
    return boost::move(t);
}
boost::thread threads[45];
threads[12]=boost::move(create_thread());

The second change is that you can now pass additional arguments to the boost::thread constructor, which are in turn passed on to the thread function:

void thread_func   (int i,double d,std::string s);
boost::thread t(thread_func, 42,3.141,"hello world");

This works exactly like boost::bind — in fact it uses boost::bind internally — so you can even pass member functions and a "this" pointer. It is important to note that the supplied arguments (including the thread function itself, if it's a function object) are copied into the newly created thread, so if you need to pass a reference you have to wrap it in boost::ref.

int i;
void thread_func(int& j);
boost::thread t   (thread_func,boost::ref(i));

For copyable types such as int, failure to use boost::ref will not be diagnosed, as the code just passes a reference to the copy of the original stored in the thread, silently giving unexpected results. Therefore, if your thread function takes its parameters by reference, you need to take extra care to ensure that boost::ref is used correctly.

Identifying Threads

Because threads can now be transferred between objects, the boost::thread object itself no longer suffices for identification. Consequently there is now a new boost::thread::id class that holds a thread ID. You can obtain the thread ID from a boost::thread object by calling the get_id() member function, or for the current thread by calling boost::this_thread::get_id().

This highlights another important difference: You can no longer get a boost::thread object that represents the "current thread." In old versions of Boost, a default-constructed boost::thread object was special, in that it represented the current thread. Now, a default-constructed boost::thread object doesn't represent any thread at all. The only use for such an object was for identifying a thread, and comparison with other boost::thread objects: This role is now taken by the boost::thread::id class.

Thread IDs sport the full range of comparison operators, and can be freely copied. If two thread IDs represent the same thread they compare equal, and if they represent different threads then they are not equal. Also, the thread IDs for distinct threads form a total order, so they can be used as keys in associative containers such as std::map<>.

boost::mutex map_mutex;
std::map<boost::thread::id, some_data> data_map;
void f()
{
    boost::mutex::scoped_lock lk(map_mutex);
    some_data& my_data=data_map[boost::this_thread::get_id()];
    lk.unlock();
    do_stuff(my_data);
}

A default-constructed boost::thread::id represents "not any thread," and will not compare equal to any boost::thread::id that holds the ID of a thread of execution.

Interrupting Threads

The final new aspect of thread management introduced with Boost 1.35.0 is the ability to interrupt a running thread. This is done by calling the interrupt() member function of the boost::thread object associated with the thread you wish to interrupt. Interruption is an asynchronous request, and is nonbinding—it merely sets a flag in the data structure associated with the thread being interrupted. When that thread reaches an interruption point, it then throws a boost::thread_interrupted exception if interruption is enabled, and clears the flag. If interruption is disabled, the exception is not thrown and the flag remains set. This is an ordinary C++ exception, and can be caught and handled just like any other exception. In this case, it is perfectly acceptable for the thread to be interrupted again. This can be useful if the thread is performing a series of tasks: If a task is interrupted, the thread can catch the interruption exception and move on to the next task.

The interruption points are:

The last one (boost::this_thread::interruption_point()) lets you place interruption points at carefully chosen places in your code, when the code would not otherwise execute an interruption point. It can be used as part of a processing loop; for example, if you are processing thousands of items, it might make sense to put an interruption point after every 10 or 100 items to enable relatively timely response to the interruption request.

Interruption is initially enabled for all threads, but can be disabled by constructing an instance of boost::this_thread::disable_interruption. Interruption is disabled for the lifetime of the disable_interruption object, but only for the thread that created the object. Typically, such an object would be created as a local variable in order to disable interruption around a specific piece of code. For more about thread interruption see my blog entry, entitled "Thread Interruption in the Boost Thread Library" (www.justsoftwaresolutions.co.uk/threading/ thread-interruption-in-boost-thread-library.html).

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 concept—which 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 price—the 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].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.