The mutex system that Threading Building Blocks includes are easy to use. However, you need to understand exactly how they work so that you don’t end up with problems such as deadlocks and locks being held open. This week Jeff Cogswell shows you the starting code for mutexes.
Last time we talked about the concept of a scoped locking pattern in parallelism. This is especially important in the case of mutexes whereby a thread can make use of a resource, and block other threads until it’s finished; after that the next thread can use the resource while other threads wait, and so on. Writing the code to make use of such a blocking mechanism can open up problems, especially if an exception occurs. If an exception happens in the thread that currently has the lock, ideally you want it to release the lock when it enters the exception handler. That way other threads are free to continue without the whole system freezing up.
The mutexes in Threading Building Blocks make use of this mechanism by existing as stack-based objects. That way when the scope ends, the code will automatically call the destructor for the object, allowing the lock to be released. Now let’s look how to use them.
As you can expect, the mutex object will be created as a local stack variable, not as an object on the heap. That means you do not call new to created it.
Now I want to be clear: In many cases you can avoid the use of mutexes, and if you can, you should. With the help of such structures as reducers, for example, and concurrent containers, you can avoid the bottlenecks that come with mutexes, and reach good thread optimization. But if you can’t avoid them, then you’ll want to make sure you use the mutexes that are part of Threading Building Blocks.
The general approach in TBB that mutexes are separated into two objects; a main mutex object, and then a scoped_lock object. The scoped_lock object is the part of the mutex that provides the scoping mechanism I described earlier. Further, the class for the scoped_lock object lives as an inner class inside the main mutex class. Both are part of the tbb namespace. So to use the classes, you’ll want to include the following:
1 #include "tbb/mutex.h"
Then if you want, you can add the tbb namespace (or simply put tbb:: before your classes):
1 using namespace tbb;
After that, you follow these steps: Create the main mutex object. Then as a thread needs the mutex, the thread creates the scoped_lock object, from which the lock is acquired. A lock can be acquired from the scoped_lock object during its construction, or later in a call to acquire. Here’s code that creates the main mutex object.
1 mutex m;
(Or, if you didn’t use namespace tbb, you would prepend mutex with tbb, as in tbb:mutex.)
Easy enough. Now when a thread needs a lock on the mutex, the thread creates an instance of a class called scoped_lock. Now here’s how you acquire the lock. You can either do it during the constructor like so:
1 mutex::scoped_lock lock(m);
or you can do it like this, first by creating the object:
1 mutex::scoped_lock lock;
and then later acquiring the lock:
The lock will release when the lock object goes out of scope, but the main reason for using that is in the event of an exception. You typically don’t want to hold onto locks any longer than necessary, and holding onto the lock until the function ends might mean holding onto it longer than you need. Therefore, to release the lock, call release:
Really, that’s it. Remember that you’ll need to create your main mutex object outside of the thread functions using the mutex, so that they can all share the same instance. But inside the thread function you’ll create the lock object, acquire the lock, and release the lock.
One important area where locks can be used is when trying to write to a resource, and the type of mutex to use in such situations is the ReaderWriterMutex. Next time I’ll take you through an example demonstrating it.