Mutexes
A mutex is an OS-level synchronization primitive that can be used
to ensure a section of code can only be executed by one thread at a time.
It has two states: locked and unlocked. When the mutex is locked,
any further attempt to lock it will block (the calling thread will be
suspended). When the mutex becomes unlocked, if there are threads waiting one
of these will be resumed and will lock the mutex. Furthermore, the mutex may
only be unlocked by the thread that locked it.
If we have a resource we need to share between threads, we
associate a mutex with it and use the mutex to synchronize resource access. All
we need to do is ensure our code locks the mutex before using the resource, and
unlocks it after it is finished. This will prevent race conditions related to
multiple threads simultaneously accessing that resource.
Diagram 1. Two thread contention for a
mutex
Note: We will be updating mutex section above soon with more documents and sample codes.
Mutexes in Practice -
Boost.Threads solution
Boost.Threads is a part of the excellent Boost libraries. It has
been intelligently designed to enhance safety by making error-prone code more
difficult to write.
We'll be using Boost.Threads throughout the tutorial, since we may
as well get used to using a well designed C++ library from the outset.
Furthermore, the upcoming C++ 0x standard (due sometime this decade) will use
Boost.Threads as the model for the new threading support, so learning this
library will help future-proof your C++ skills.
Listing 2. Boost.Threads synchronisation
int sharedCounter = 50;
boost::mutex counterMutex;
void solutionWorkerThread()
{
while(sharedCounter > 0)
{
bool doWork = false;
{
// scoped_lock locks mutex
boost::mutex::scoped_lock lock(counterMutex);
if(sharedCounter > 0)
{
--sharedCounter;
doWork = true;
}
// scoped_lock unlocks mutex automatically at end of scope
}
if(doWork) doSomeWork();
}
}
In the above solution, the shared counter is checked and updated
as an atomic operation (with respect to multiple threads) so the race condition
is solved.
Note the way the scoped_lock works: the constructor locks the
associated mutex and the destructor unlocks it. This is the RAII (Resource Acquisition
Is Initialization idiom, and it helps exception safety. If an exception
were thrown while we had locked the mutex, the scoped_lock would be destroyed
during the normal stack unwinding process and the mutex would be automatically
freed.
Exception safety is not an issue with this simple example, since
no statement can throw while we have the mutex locked. However, real-world code
will almost always benefit from the scoped_lock design.
Unfortunately concurrent code can have many problems: race conditions
are only the most fundamental. The next problem we'll cover is called Deadlock,
and it commonly arises from the interaction of blocking mutex.
See also:
No comments:
Post a Comment