Threads Module User's Guide : PART II Concurrency Packages : Chapter 4 The Synchronization Package : The Synchronization Classes : The RWMutexLock Class
The RWMutexLock Class
An RWMutexLock implements a form of synchronization called mutual exclusion, where the presence of one thread within a protected or critical section of code prohibits the entry of other threads into that section.
A thread must acquire ownership of a mutex prior to entering the section of code protected by the mutex. This is accomplished by calling the acquire() method of the mutex. Once a thread is granted ownership of the mutex, it is allowed to proceed into the protected code. Other threads attempting to enter this same section of code also attempt to acquire ownership of the mutex, but these threads are forced to wait until the current owner exits the protected section of code and releases ownership of the mutex.
A thread relinquishes its ownership of a mutex by calling its release() method.
Avoiding a Block
To avoid blocking during the acquisition of a mutex that is already owned by another thread, use the tryAcquire() method. This function returns false if the mutex is already owned by another thread and returns true if the mutex has been successfully acquired.
The interactions between these functions are illustrated using the interaction diagram shown in Figure 24:
Figure 24 – RWMutexLock interactions
Using Timed Waits
In some environments, RWMutexLock supports timed acquisition of a mutex. This capability is included in a version of the acquire() function that accepts a time limit for waiting, specified as an unsigned long number of milliseconds.
In those environments that do not support a timed wait on a mutex, the timed version of acquire() attempts a conditional acquisition of the mutex, and if the mutex is not immediately available, the function returns a value indicating timeout. The macro RW_THR_HAS_TIMED_MUTEX_ACQUIRE can be used to determine whether or not the current environment supports true timeouts for mutex acquisition; this macro is defined when timed waits are supported.
Attempting Recursive Acquisition
RWMutexLock does not support recursive acquisition. In a debug version of the Synchronization package, any attempt to recursively acquire a mutex produces an assertion that aborts the process. In the release version, a recursive acquisition can succeed or can result in deadlock. The specific behavior is defined by the underlying threads system. If recursive acquisition semantics are required, use the RWTRecursiveLock template class to create a mutex that allows for it.
Acquiring Mutex Ownership
If the order in which threads acquire a mutex are the same order in which ownership is granted, then first-in-first-out or FIFO ordering is being used. The RWMutexLock class does not ensure FIFO ordering.
Different systems use different policies for granting mutex ownership. Some prioritize acquisition requests based on the priority of the thread; others can maintain FIFO ordering (unless the thread is interrupted for some reason, in which case its position in any internal queue can be changed).
If you require FIFO acquisition semantics regardless of a thread’s priority or any other factors, then use the RWFIFOMutexLock class.
An RWMutexLock is commonly used to protect data or resources by limiting access to one thread at a time. Access is typically controlled by requiring that a mutex be acquired prior to reading or manipulating the data or resource. Once the actual access is completed, the mutex should then be released.
Example 32 illustrates this concept by implementing a simple thread-safe queue class based on a linked list class from the Essential Tools Module.
Example 32 – Using a mutex to prevent interference between threads
#include <rw/sync/RWMutexLock.h>
#include <rw/tvslist.h>
template <class T>
class Queue {
private:
RWMutexLock mutex_;
RWTValSlist<T> list_;
public:
void enqueue(const T& t) {
mutex_.acquire();
list_.append(t);
mutex_.release();
}
T dequeue(void) {
mutex_.acquire();
T result = list_.removeFirst(t);
mutex_.release();
return result;
}
}
“Using Mutexes” discussed the disadvantages of explicitly coding the mutex acquisition and release, which was done in this example. If either linked-list member function or the copy constructor for the template parameter class throws an exception, the Queue’s mutex is not released.
Using a Guard Class
Try-catch blocks can be added to correct this problem, but it is much easier to use a guard class instance to automatically acquire and release the mutex at construction and destruction.
By using the lock guard defined by the RWMutexLock class, this example can be rewritten as in Example 33.
Example 33 – Using a guard to acquire and release a mutex
template <class T>
class Queue {
private:
RWMutexLock mutex_;
RWTValSlist<T> list_;
public:
void enqueue(const T& t) {
RWMutexLock::LockGuard lock(mutex_);
list_.append(t);
}
T dequeue(void) {
RWMutexLock::LockGuard lock(mutex_);
return list_.removeFirst(t);
}
}
In this version, the mutex is always released, even if an exception is thrown by one of the list members or by the template parameter class.
In addition to the public typedef for LockGuard, RWMutexLock also defines typedefs for UnlockGuard, TryLockGuard, ReadLockGuard, WriteLockGuard, TryReadLockGuard, TryWriteLockGuard, ReadUnlockGuard, and WriteUnlockGuard.
Initializing a Mutex
Normally, a mutex is initialized by its constructor before an acquire operation can occur. However, a mutex declared at global scope can be accessed before it is constructed, if threads are launched during the static initialization phase. Therefore, global static instances of RWMutexLock can require a different style of initialization.
To account for this possibility, the mutex acquire operation is designed to check for proper initialization each time it is called. If the mutex has not been initialized, the acquire operation performs the initialization itself. This allows a thread to safely acquire a mutex prior to its actual construction.
If a global mutex is declared using the normal constructor and that mutex is accessed before construction, then the mutex is initialized a second time when it is finally constructed.
To avoid the possibility of an erroneous second initialization, you must use a special constructor that does not initialize the mutex. This static constructor is selected by initializing the mutex with a special enumerated constant, RW_STATIC_CTOR, as shown in this declaration:
 
// Declare a global static mutex instance
RWMutexLock mutex(RW_STATIC_CTOR);
The RWMutexLock static constructor must only be used for global static instances. Use of this constructor with an automatically or dynamically allocated instance produces errors or other unpredictable behavior.
NOTE >> The initialization of a global static mutex is not thread-safe. It is conceivable that two threads can attempt to acquire and initialize a mutex simultaneously, resulting in a race condition. When designing your application, you must take care to avoid such a possibility.
Servicing Cancellation Requests
The synchronization classes can automatically service cancellation requests before performing any operation that can result in blocking the calling thread. If you want to do this, enable cancellation processing for the synchronization object when you construct it by passing the appropriate RWCancellationState value to the constructor.
The default cancellation state of a synchronization object is RW_CANCELLATION_DISABLED. You can override that default by specifying the Threading package RW_CANCELLATION_ENABLED state when you construct the synchronization instance, as shown in Example 34.
Example 34 – Enabling cancellation request service at instantiation
RWMutexLock mutex(RW_CANCELLATION_ENABLED);
 
try {
mutex.acquire(); // May throw cancellation!
}
catch(RWCancellation&) {
// Do something about it!
}
You can also enable and disable synchronization cancellation processing as shown in Example 35. This approach uses the setCancellation() member function included in the RWSynchObject class, which is the base class for all synchronization mechanisms.
Example 35 – Enabling automatic servicing of cancellation requests
#include <rw/thread/RWCancellation.h>
 
RWMutexLock mutex; // Defaults to disabled!
 
mutex.setCancellation(RW_CANCELLATION_ENABLED);
try {
mutex.acquire(); // May throw cancellation!
}
catch(RWCancellation&) {
// Do something about it!
}
This example requires the Threading package. SeeChapter 3, “The Threading Package,” for more information on cancellation.