|
Boost.ThreadsMutex Concepts |
Introduction
Locking Strategies
Recursive
Checked
Unchecked
Unspecified
Scheduling Policies
FIFO
Priority Driven
Undefined
Unspecified
Concept Requirements
Mutex Concept
TryMutex Concept
TimedMutex Concept
Models
A mutex (short for mutual-exclusion) concept serializes access to a resource shared between multiple threads. The Mutex concept, with TryMutex and TimedMutex refinements, formalize the requirements. A model that implements Mutex and its refinements has two states: locked and unlocked. Before using a shared resource, a thread locks a Boost.Threads mutex model object, insuring thread-safe access to the shared resource. When use of the shared resource is complete, the thread unlocks the mutex model object, allowing another thread to acquire the lock and use the shared resource.
Traditional C thread APIs, like Pthreads or the Windows thread APIs, expose functions to lock and unlock a mutex model. This is dangerous since it's easy to forget to unlock a locked mutex. When the flow of control is complex, with multiple return points, the likelihood of forgetting to unlock a mutex model would become even greater. When exceptions are thrown, it becomes nearly impossible to ensure that the mutex is unlocked properly when using these traditional API's. The result is deadlock.
Many C++ threading libraries use a pattern known as Scoped Locking [Schmidt 00] to free the programmer from the need to explicitly lock and unlock mutexes. With this pattern, a lock concept is employed where the lock model's constructor locks the associated mutex model and the destructor automatically does the unlocking. The Boost.Threads library takes this pattern to the extreme in that lock concepts are the only way to lock and unlock a mutex model: lock and unlock functions are not exposed by any Boost.Threads mutex models. This helps to ensure safe usage patterns, especially when code throws exceptions.
Every mutex model follows one of several locking strategies. These strategies define the semantics for the locking operation when the calling thread already owns a lock on the mutex model.
With a recursive locking strategy when a thread attempts to acquire a lock on the mutex model for which it already owns a lock, the operation is successful. Note the distinction between a thread, which may have multiple locks outstanding on a recursive mutex, and a lock object, which even for a recursive mutex cannot have its lock() function called multiple times without first calling unlock().
Internally a lock count is maintained and the owning thread must unlock the mutex model the same number of times that it's locked it before the mutex model's state returns to unlocked. Since mutex models in Boost.Threads expose locking functionality only through lock concepts, a thread will always unlock a mutex model the same number of times that it locked it. This helps to eliminate a whole set of errors typically found in traditional C style thread APIs.
Classes recursive_mutex, recursive_try_mutex and recursive_timed_mutex use this locking strategy.
With a checked locking strategy when a thread attempts to acquire a lock on the mutex model for which the thread already owns a lock, the operation will fail with some sort of error indication. Further, attempts by a thread to unlock a mutex that was not locked by the thread will also return some sort of error indication. In Boost.Threads, an exception of type lock_error would be thrown in these cases.
Boost.Threads does not currently provide any mutex models that use this strategy.
With an unchecked locking strategy when a thread attempts to acquire a lock on the mutex model for which the thread already owns a lock the operation will deadlock. In general this locking strategy is less safe than a checked or recursive strategy, but it's also a faster strategy and so is employed by many libraries.
Boost.Threads does not currently provide any mutex models that use this strategy.
With an unspecified locking strategy, when a thread attempts to acquire a lock on a mutex model for which the thread already owns a lock the operation results in undefined behavior. When a mutex model has an unspecified locking strategy the programmer must assume that the mutex model instead uses an unchecked strategy.
In general a mutex model with an unspecified locking strategy is unsafe, and it requires programmer discipline to use the mutex model properly. However, this strategy allows an implementation to be as fast as possible with no restrictions on its implementation. This is especially true for portable implementations that wrap the native threading support of a platform. For this reason, the classes mutex, try_mutex and timed_mutex use this locking strategy despite the lack of safety.
Every mutex model follows one of several scheduling policies. These policies define the semantics when the mutex model is unlocked and there is more than one thread waiting to acquire a lock. In other words, the policy defines which waiting thread shall acquire the lock.
With a FIFO scheduling policy, threads waiting for the lock will acquire it in a first come first serve order (or First In First Out). This can help prevent a high priority thread from starving lower priority threads that are also waiting on the mutex lock.
With a Priority Driven scheduling policy, the thread with the highest priority acquires the lock. Note that this means that low-priority threads may never acquire the lock if the mutex model has high contention and there is always at least one high-priority thread waiting. This is known as thread starvation. When multiple threads of the same priority are waiting on the mutex lock one of the other scheduling priorities will determine which thread shall acquire the lock.
Threads acquire the lock in no particular order. Users should assume that low-priority threads may wait indefinitely, and that threads of the same priority acquire the lock in essentially random order.
The mutex model does not specify which scheduling policy is used. The programmer must assume that an undefined scheduling policy is used. In order to ensure portability, all Boost.Threads mutex models use an unspecified scheduling policy.
A Mutex object has two states: locked and unlocked. Mutex object state can only be determined by an object meeting the ScopedLock requirements and constructed for the Mutex object.
A Mutex is noncopyable.
For a Mutex type M and an object m of that type, the following expressions must be well-formed and have the indicated effects.
Expression | Effects |
M m; |
Constructs a mutex object m. Post-condition: m is unlocked. |
(&m)->~M(); |
Precondition: m is unlocked. Destroys a mutex object m. |
M::scoped_lock |
A type meeting the ScopedLock requirements. |
A TryMutex must meet the Mutex requirements. In addition, for a TryMutex type M and an object m of that type, the following expressions must be well-formed and have the indicated effects.
Expression | Effects |
M::scoped_try_lock |
A type meeting the ScopedTryLock requirements. |
A TimedMutex must meet the TryMutex requirements. In addition, for a TimedMutex type M and an object m of that type, the following expressions must be well-formed and have the indicated effects.
Expression | Effects |
M::scoped_timed_lock |
A type meeting the ScopedTimedLock requirements. |
Boost.Threads currently supplies six classes which model mutex concepts.
Concept | Refines | Classes Modeling the Concept |
Mutex | mutex recursive_mutex |
|
TryMutex | Mutex | try_mutex recursive_try_mutex |
TimedMutex | TryMutex | timed_mutex recursive_timed_mutex |
Revised 01 October, 2001
© Copyright William E. Kempf 2001 all rights reserved.