Threads Module User's Guide : PART II Concurrency Packages : Chapter 4 The Synchronization Package : Getting Started : Building Monitors
Building Monitors
A monitor is a passive object that has a synchronized interface for accessing a shared resource. A simple form of monitor can be constructed by encapsulating a resource or data in a C++ class that also includes a mutex. Each member function that gives access to the resource or data uses the mutex to synchronize or serialize the access operations.
In Example 29, a simple monitor class is constructed to implement a multithread-safe counter.
Example 29 – Implementing a multithread-safe counter
#include <rw/sync/RWMutexLock.h>
 
class Counter {
private:
RWMutexLock mutex;
int count_;
public:
Counter(int count=0) : count_(count) {}
Counter& operator++(void)
{
RWMutexLock::LockGuard guard(mutex);
count_++;
return *this;
}
Counter& operator--(void)
{
RWMutexLock::LockGuard guard(mutex);
count_--;
return *this;
}
operator int(void) const
{
RWMutexLock::LockGuard guard(mutex);
return count_;
}
};
In this example, each member function that accesses the count member first acquires the mutex to insure that other threads do not simultaneously attempt to change the count value.
NOTE >> Instead of the RWTLockGuard class, this example uses the public type LockGuard that is defined by the RWMutexLock class. All of the Synchronization package classes have pre-defined guard types.
Count Synchronization
Count synchronization is necessary because, in all likelihood, the integer increment and decrement operators are not atomic. A compiler must typically generate a non-atomic sequence of instructions to read the count variable, increment it, then write it back. Without synchronization, two different threads could interleave these sequences producing undesirable results, as illustrated in Figure 23.
Figure 23 – Count synchronization execution scenario
The use of the mutex to protect the counter operations ensures that each read-modify-write sequence is not interleaved with counter instruction sequences executing in other threads.
Read Synchronization
In some environments, the operator int() function is atomic because an int value can always be fetched with a single indivisible memory access (see the previous example). However, this is only true if the environment or compiler forces the count member to be aligned on a word boundary.
Many architectures allow data fetches across word boundaries. These fetches are typically non-atomic, and while most compilers do allow you to control the word alignment for class and structure members, the typical default is to pack the members without regard to alignment. For this reason, you should synchronize any read operations involving data types whose size is greater than the smallest indivisible memory fetch (usually a byte).
The RWTMonitor<Lock> Class
To simplify the development of monitor classes, the Synchronization package defines a template class, RWTMonitor<Lock>, that can be used as a base class in new monitor class implementations. This class includes a mutex member and has public definitions for LockGuard and UnlockGuard types that accept a self-reference returned by a monitor() member function.
Using the RWTMonitor as a base class gives the implementation in Example 30.
Example 30 – Deriving from RWTMonitor
#include <rw/sync/RWMutexLock.h>
#include <rw/sync/RWTMonitor.h>
 
class Counter : public RWTMonitor<RWMutexLock> {
private:
int count;
public:
Counter(int count=0) : count(count) {}
Counter& operator++(void)
{
RWMutexLock::LockGuard lock(monitor());
count++;
return *this;
}
Counter& operator--(void)
{
RWMutexLock::LockGuard lock(monitor());
count--;
return *this;
}
operator int(void) const
{
RWMutexLock::LockGuard lock(monitor());
return count;
}
};