Monitor Object 1 Intent 2 Also Known As 3 Example
Monitor Object
An Object Behavioral Pattern for
Concurrent Programming
Douglas C. Schmidt
schmidt@cs.wustl.edu
Department of Computer Science
Washington University, St. Louis
1
Intent
the corresponding consumer handler, whose thread then de-
livers the message to its remote consumer.1
The Monitor Object pattern synchronizes method execution
When suppliers and consumers reside on separate hosts,
to ensure only one method runs within an object at a time.
the Gateway uses the connection-oriented TCP [2] proto-
It also allows an object’s methods to cooperatively schedule
col to provide reliable message delivery and end-to-end flow
their execution sequences.
control. TCP’s flow control algorithm blocks fast senders
when they produce messages more rapidly than slower re-
ceivers can process the messages. The entire Gateway should
2
Also Known As
not block, however, while waiting for flow control to abate
on outgoing TCP connections. To minimize blocking, there-
Thread-safe Passive Object
fore, each consumer handler can contain a thread-safe mes-
sage queue that buffers new routing messages it receives
3
Example
from its supplier handler threads.
One way to implement a thread-safe Message Queue
is to use the Active Object pattern [1], which decouples the
Let’s reconsider the design of the communication Gateway
thread used to invoke a method from the thread used to exe-
described in the Active Object pattern [1] and shown in
cute the method. As shown in Figure 2, each message queue
Figure 1. The Gateway process contains multiple supplier
active object contains a bounded buffer and its own thread of
control that maintains a queue of pending messages. Using
Supplier
Routing
Consumer
Handler
Table
Handler
Consumer
2: find (msg)
1: put (msg)
Handler
3: put (msg)
2: put (msg)
Supplier
Message Queue
Consumer
Handler
Handler
4: get (msg)
Supplier
5: send (msg)
Handler
1: recv (msg)
3: get (msg)
4: send (msg)
INCOMING
OUTGOING
INCOMING
GATEWAY MESSAGES
MESSAGES
MESSAGES
OUTGOING
GATEWAY
MESSAGES
OUTGOING MESSAGES
Figure 2: Implementing Message Queues as Active Objects
SUPPLIER
CONSUMER
CONSUMER
SUPPLIER
the Active Object pattern to implement a thread-safe mes-
Figure 1: Communication Gateway
sage queue decouples supplier handler threads in the Gate-
way process from consumer handler threads so all threads
handler and consumer handler objects that run in separate
can run concurrently and block independently when flow
threads and route messages from one or more remote sup-
control occurs on various TCP connections.
pliers to one or more remote consumers, respectively. When
1
For an in-depth discussion of the Gateway and its associated compo-
a supplier handler thread receives a message from a remote
nents, we recommend you read the Active Object pattern before reading the
supplier, it uses an address field in the message to determine
Monitor Object pattern.
1
Although the Active Object pattern can be used to imple-
should be responsible for ensuring that any of their methods
ment a functional Gateway, it has the following drawbacks:
requiring synchronization are serialized transparently, i.e.,
without explicit client intervention.
Performance overhead:
The Active Object pattern pro-
vides a powerful concurrency model. It not only synchro-
3. Objects should be able to schedule their methods coop-
nizes concurrent method requests on an object, but also can
eratively:
If an object’s methods must block during their
perform sophisticated scheduling decisions to determine the
execution, they should be able to voluntarily relinquish their
order in which requests execute. These features incur non-
thread of control so that methods called from other client
trivial amounts of context switching, synchronization, dy-
threads can access the object. This property helps prevent
namic memory management, and data movement overhead,
deadlock and makes it possible to leverage the concurrency
however, when scheduling and executing method requests.
available on hardware/software platforms.
Programming overhead:
The Active Object pattern re-
quires programmers to implement up to six components:
6
Solution
proxies, method requests, an activation queue, a scheduler, a
servant, and futures for each proxy method. Although some
For each object accessed concurrently by client threads de-
components, such as activation queues and method requests,
fine it as a monitor object. Clients can access the services
can be reused, programmers may have to reimplement or sig-
defined by a monitor object only through its synchronized
nificantly customize these components each time they apply
methods. To prevent race conditions involving monitor ob-
the pattern.
ject state, only one synchronized method at a time can run
In general, the performance and programming overhead
within a monitor object. Each monitored object contains a
outlined above can be unnecessarily expensive if an applica-
monitor lock that synchronized methods use to serialize their
tion does not require all the Active Object pattern features,
access to an object’s behavior and state. In addition, syn-
particularly its sophisticated scheduling support. Yet, pro-
chronized methods can determine the circumstances under
grammers of concurrent applications must ensure that certain
which they suspend and resume their execution based on one
method requests on objects are synchronized and/or sched-
or more monitor conditions associated with a monitor object.
uled appropriately.
7
Structure
4
Context
There are four participants in the Monitor Object pattern:
Applications where multiple threads of control access ob-
Monitor object
jects simultaneously.
A monitor object exports one or more methods to
clients.
To protect the internal state of the monitor
5
Problem
object from uncontrolled changes or race conditions,
all clients must access the monitor object only through
Many applications contain objects that are accessed concur-
these methods. Each method executes in the thread of
rently by multiple client threads. For concurrent applications
the client that invokes it because a monitor object does
to execute correctly, therefore, it is often necessary to syn-
not have its own thread of control.2
chronize and schedule access to these objects. In the pres-
For instance, the consumer handler’s message queue in
ence of this problem, the following three requirements must
the Gateway application can be implemented as a mon-
be satisfied:
itor object.
1.
Synchronization boundaries should correspond to
object methods:
Object-oriented programmers are accus-
Synchronized methods
tomed to accessing an object only through its interface meth-
Synchronized methods implement the thread-safe ser-
ods in order to protect an object’s data from uncontrolled
vices exported by a monitor object. To prevent race
changes. It is relatively straightforward to extend this object-
conditions, only one synchronized method can execute
oriented programming model to protect an object’s data from
within a monitor at any point in time, regardless of the
uncontrolled concurrent changes, known as race conditions.
number of threads that invoke the object’s synchronized
Therefore, an object’s method interface should define its syn-
methods concurrently or the number of synchronized
chronization boundaries.
methods in the object’s class.
2. Objects, not clients, should be responsible for their
For instance, the put and get operations on the con-
own method synchronization:
Concurrent applications
sumer handler’s message queue should be synchronized
are harder to program if clients must explicitly acquire
methods to ensure that routing messages can be inserted
and release low-level synchronization mechanisms, such as
semaphores, mutexes, or condition variables. Thus, objects
2
In contrast, an active object [1] does have its own thread of control.
2
and removed simultaneously by multiple threads with-
1.
Synchronized method invocation and serialization:
out corrupting a queue’s internal state.
When a client invokes a synchronized method on a moni-
tor object, the method must first acquire its monitor lock. A
Monitor lock
monitor lock cannot be acquired as long as another synchro-
Each monitor object contains its own monitor lock.
nized method is executing within the monitor object. In this
Synchronized methods use this monitor lock to seri-
case, the client thread will block until it acquires the monitor
alize method invocations on a per-object basis. Each
lock, at which point the synchronized method will acquire
synchronized method must acquire/release the monitor
the lock, enter its critical section, and perform the service
lock when the method enters/exits the object, respec-
implemented by the method. Once the synchronized method
tively. This protocol ensures the monitor lock is held
has finished executing, the monitor lock must be released so
whenever a method performs operations that access or
that other synchronized methods can access the monitor ob-
modify its object’s state.
ject.
For instance, a Thread Mutex [3] could be used to
implement the message queue’s monitor lock.
2. Synchronized method thread suspension:
If a syn-
chronized method must block or cannot otherwise make im-
Monitor condition
mediate progress, it can wait on one of its monitor condi-
Multiple synchronized methods running in separate
tions, which causes it to “leave” the monitor object temporar-
threads can cooperatively schedule their execution se-
ily [5]. When a synchronized method leaves the monitor
quences by waiting for and notifying each other via
object, the monitor lock is released automatically and the
monitor conditions associated with their monitor object.
client’s thread of control is suspended on the monitor con-
Synchronized methods use monitor conditions to deter-
dition.
mine the circumstances under which they should sus-
pend or resume their processing.
3.
Method condition notification:
A synchronized
For instance, when a consumer handler thread attempts
method can notify a monitor condition in order to resume a
to dequeue a routing message from an empty message
synchronized method’s thread that had previously suspended
queue, the queue’s get method must release the mon-
itself on the monitor condition. In addition, a synchronized
itor lock and suspend itself until queue is no longer
method can notify all other synchronized methods that pre-
empty, i.e., when a supplier handler thread inserts a
viously suspended their threads on a monitor condition.
message in it. Likewise, when a supplier handler thread
attempts to enqueue a message into a full queue, the
4.
Synchronized method thread resumption:
Once a
queue’s put method must release the monitor lock and
previously suspended synchronized method thread is noti-
spend itself until the queue is no longer full, i.e., when
fied, its execution can resume at the point where it waited
a consumer handler removes a message from it. A pair
on the monitor condition. The monitor lock is automatically
of POSIX condition variables [4] can be used to imple-
reacquired before the notified thread “reenters” the monitor
ment the message queue’s not-empty and not-full moni-
object and resumes executing the synchronized method.
tor conditions.
The structure of the Monitor Object pattern is illustrated
The following figure illustrates the collaborations in the
in the following UML class diagram:
Monitor Object pattern:
Monitor Object
: client
: client
: monitor : monitor : monitor
thread 1
thread 2
object
lock
condition
synchronized_method_1()
method_1()
...
SYNCHRONIZED
METHOD INVOCATION
acquire()
synchronized_method_m()
& SERIALIZATION
do_work()
monitor_lock_
wait()
SYNCHRONIZED METHOD
monotor_condition_1_
THREAD SUSPENSION
release()
suspend thread 1
...
method_2()
monitor_condition_n_
acquire()
METHOD CONDITION
NOTIFICATION
do_work()
notify()
method_2() release()
return
resume thread 1
SYNCHRONIZED METHOD
resume method_1()
THREAD RESUMPTION
acquire()
8
Dynamics
do_work()
method_1() return
release()
The following collaborations occurs between participants in
the Monitor Object pattern.
3
9
Implementation
concerns helps to decouple synchronization and scheduling
logic from monitor object functionality, as well as avoid
The following steps illustrate how to implement the Monitor
intra-object deadlock and unnecessary locking overhead.
Object pattern.
The following conventions based on the Thread-safe In-
1. Define the monitor object’s interface methods:
The
terface idiom [6] can be used to structure the separation of
interface of a monitor object exports a set of methods to
concerns between interface and implementation methods:
clients. Interface methods are typically synchronized, i.e.,
Interface methods only acquire/release monitor locks
only one of them at a time can execute in a particular moni-
and wait/notify certain monitor conditions, and then
tor object.
forward to implementation methods that perform the
In our Gateway example, each consumer handler contains
monitor object’s functionality.
a message queue and a TCP connection. The message queue
can be defined as a monitor object that buffer messages
Implementation methods only perform work when
it receives from supplier handler threads. Monitor objects
called by interface methods, i.e., they do not ac-
can help prevent the entire Gateway process from blocking
quire/release the monitor lock or wait/notify monitor
whenever consumer handler threads encounter flow control
conditions explicitly. Moreover, to avoid intra-object
on TCP connections to their remote consumers.
method deadlock or unnecessary synchronization over-
The following C++ class defines the interface for a mes-
head, implementation methods should not call any syn-
sage queue monitor object:
chronized methods defined in their monitor object’s in-
terface.
class Message_Queue
{
In our Gateway example, the Message Queue class
public:
defines four implementation methods:
put i, get i,
enum {
MAX_MESSAGES = /* ... */;
empty i, and full i, corresponding to the synchronized
};
method interface. The signatures of these methods are shown
below:
// The constructor defines the maximum number
// of messages in the queue.
This determines
class Message_Queue
// when the queue is ‘full.’
{
Message_Queue (size_t max_messages
public:
= MAX_MESSAGES);
// ... See above ....
// = Message queue synchronized methods.
private:
// = Private helper methods (non-synchronized
// Put the <Message> at the tail of the queue.
//
and do not block).
// If the queue is full, block until the queue
// is not full.
// Put the <Message> at the tail of the queue.
void put (const Message &msg);
void put_i (const Message &msg);
// Get the <Message> at the head of the queue.
// Get the <Message> at the head of the queue.
// If the queue is empty, block until the queue
Message get_i (void);
// is not empty.
Message get (void);
// True if the queue is full, else false.
// Assumes locks are held.
// True if the queue is full, else false.
bool empty_i (void) const;
// Does not block.
bool empty (void) const;
// True if the queue is empty, else false.
// Assumes locks are held.
// True if the queue is empty, else false.
bool full_i (void) const;
// Does not block.
bool full (void) const;
// ...
private:
The implementation methods are typically not synchronized,
// ...
nor do they block, in accordance with the Thread-safe Inter-
};
face idiom [6] outlined above.
The Message Queue monitor object interface exports four
3. Define the method object’s internal state:
A monitor
synchronized methods. The empty and full methods are
object contains data members that define its internal state. In
predicates that clients can use to distinguish three internal
addition, a monitor object contains a monitor lock that se-
states: (1) empty, (2) full, and (3) neither empty nor full. The
rializes the execution of its synchronized methods and one
put and get methods enqueue and dequeue Messages
or more monitor conditions used to schedule synchronized
into and from the queue, respectively, and will block if the
method execution within a monitor object. There is typically
queue is full or empty.
a separate monitor condition for each type of situation where
2. Define the method object’s implementation methods:
synchronized methods must suspend themselves and/or re-
A monitor object often contains implementation methods
sume other threads whose synchronized methods are sus-
that simplify its interface methods.
This separation of
pended.
4
A monitor lock can be implemented using a mutex. A mu-
};
tex makes collaborating threads wait while the thread hold-
ing the mutex executes code in a critical section.
Mon-
A Message Queue monitor object defines three types of
itor conditions can be implemented using condition vari-
internal state:
ables [4]. Unlike a mutex, a condition variable is used by
Queue representation data members:
These data
a thread to make itself wait until an arbitrarily complex con-
members define the internal queue representation. This rep-
dition expression involving shared data attains a particular
resentation stores the contents of the queue in a circular array
state.
or linked list, along with bookkeeping information needed
A condition variable is always used in conjunction with
to determine whether the queue is empty, full, or neither.
a mutex, which the client thread must acquire before evalu-
The internal queue representation is accessed and manipu-
ating the condition expression. If the condition expression
lated only by the get i, put i, empty i, and full i
is false, the client atomically suspends itself on the condi-
implementation methods.
tion variable and releases the mutex so that other threads can
change the shared data. When a cooperating thread changes
Monitor lock data member:
The monitor lock is
this data, it can notify the condition variable, which atomi-
used by a Message Queue’s synchronized methods to se-
cally resumes a thread that had previously suspended itself
rialize their access to a monitor object. The monitor lock
on the condition variable and acquires its mutex again.
is implemented using the Thread Mutex defined in the
With its mutex held, the newly resumed thread then re-
Wrapper Facade pattern [3]. This class provides a platform-
evaluates its condition expression. If the shared data has at-
independent mutex API.
tained the desired state the thread continues. Otherwise, it
Monitor condition data members:
The monitor con-
suspends itself on the condition variable again until it’s re-
ditions that the put and get synchronized methods use to
sumed. This process can repeat until the condition expres-
suspend and resume themselves when a Message Queue
sion becomes true.
transitions between its full and empty boundary conditions,
In general, a condition variable is more appropriate than
respectively. These monitor conditions are implemented us-
a mutex for situations involving complex condition expres-
ing the Thread Condition wrapper facade defined be-
sions or scheduling behaviors. For instance, condition vari-
low:
ables can be used to implement thread-safe message queues.
In this use case, a pair of condition variables can coopera-
class Thread_Condition
tively block supplier threads when a message queue is full
{
public:
and block consumer threads when the queue is empty.
// Initialize the condition variable and
In our Gateway example, the Message Queue defines
// associate it with the <mutex_>.
Thread_Condition (const Thread_Mutex &m)
its internal state as illustrated below:
// Implicitly destroy the condition variable.
class Message_Queue
˜Thread_Condition (void);
{
// ... See above ....
// Wait for the <Thread_Condition> to be,
// notified or until <timeout> has elapsed.
// If <timeout> == 0 wait indefinitely.
private:
int wait (Time_Value *timeout = 0) const;
// Internal Queue representation.
// Notify one thread waiting on the
...
// <Thread_Condition>.
int notify (void) const;
// Current number of <Message>s in the queue.
size_t message_count_;
// Notify *all* threads waiting on
// the <Thread_Condition>.
// The maximum number <Message>s that can be
int notify_all (void) const;
// in a queue before it’s considered ‘full.’
private:
size_t max_messages_;
#if defined (_POSIX_PTHREAD_SEMANTICS)
pthread_cond_t cond_;
// = Mechanisms required to implement the
#else
// monitor object’s synchronization policies.
// Condition variable emulations.
#endif /* _POSIX_PTHREAD_SEMANTICS */
// Mutex that protect the queue’s internal state
// from race conditions during concurrent access.
// Reference to mutex lock.
mutable Thread_Mutex monitor_lock_;
const Thread_Mutex &mutex_;
};
// Condition variable used to make synchronized
// method threads wait until the queue is no
The constructor initializes the condition variable and as-
// longer empty.
sociates it with the Thread Mutex passed as a parame-
Thread_Condition not_empty_;
ter. The destructor destroys the condition variable, which
// Condition variable used to make synchronized
release any resources allocated by the constructor. Note that
// method threads wait until the queue is
the mutex is not owned by the Thread Condition, so
// no longer full.
Thread_Condition not_full_;
it is not destroyed in the destructor.
5
When called by a client thread, the wait method (1)
monitor lock is held and simply check for the bound-
atomically releases the associated mutex and (2) sus-
ary conditions in the queue:
pends itself for up to timeout amount of time waiting
bool
for the Thread Condition object to be notified by an-
Message_Queue::empty_i (void) const
other thread.
The notify method resumes one thread
{
return message_count_ <= 0;
waiting on a Thread Condition and the notify all
}
method notifies all threads that are currently waiting on a
bool
Thread Condition. The mutex lock is reacquired by
Message_Queue::full_i (void) const
the wait method before it returns to its client thread, e.g.,
{
either because the condition variable was notified or because
return message_count_ > max_messages_;
}
its timeout expired.
4. Implement all the monitor object’s methods and data
The put method inserts a new Message at the tail of a
members:
The final step involves implementing all the
queue. It is a synchronized method that illustrates a more
monitor object methods and internal state defined above.
sophisticated use of the Thread-safe Interface idiom:
These steps can be further decomposed as follows:
void
Message_Queue::put (const Message &msg)
Initialize the data members:
This substep initializes
{
object-specific data members, as well as the monitor lock and
// Use the Scoped Locking idiom to
any monitor conditions.
// acquire/release the <monitor_lock_> upon
// entry/exit to the synchronized method.
For instance, the constructor of Message Queue creates
Guard<Thread_Mutex> guard (monitor_lock_);
an empty message queue and initializes the monitor condi-
// Wait while the queue is full.
tions, not empty and not full , as shown below.
while (full_i ()) {
Message_Queue::Message_Queue (size_t max_messages)
// Release <monitor_lock_> and suspend our
: not_full_ (monitor_lock_),
// thread waiting for space to become available
not_empty_ (monitor_lock_),
// in the queue.
The <monitor_lock_> is
max_messages_ (max_messages),
// reacquired automatically when <wait> returns.
message_count_ (0)
not_full_.wait ();
{
}
// ...
}
// Enqueue the <Message> at the tail of
// the queue and update <message_count_>.
put_i (new_item);
In this example, now how both monitor conditions share
the same monitor lock .
This design ensures that
// Notify any thread waiting in <get> that
// the queue has at least one <Message>.
Message Queue state, such as the message count , is
not_empty_.notify ();
serialized properly to prevent race conditions when multiple
// Destructor of <guard> releases <monitor_lock_>.
threads put and get messages into a queue simultaneously.
}
Apply the Thread-safe Interface idiom:
In this sub-
Note how this synchronized method only performs the syn-
step, the interface and implementation methods are imple-
chronization and scheduling logic needed to serialize access
menting according to the Thread-safe Interface idiom.
to the monitor object and wait while the queue is full, re-
For instance, the following Message Queue methods
spectively. Once there’s room in the queue, it forwards to
check if a queue is empty, i.e., contains no Messages at
the put i method, which inserts the message into the queue
all, or full i.e., contains more than max messages in it.
and updates the bookkeeping information. Moreover, the
We show the interface methods first:
put i need not be synchronized because the put method
bool
never calls it without first acquiring the monitor lock .
Message_Queue::empty (void) const
Likewise, the put i method need not check to see if the
{
Guard<Thread_Mutex> guard (monitor_lock_);
queue is full because it is never called as long as full i
return empty_i ();
returns true.
}
The get method removes the Message from the front of
bool
a queue and returns it to the caller.
Message_Queue::full (void) const
{
Message
Guard<Thread_Mutex> guard (monitor_lock_);
Message_Queue::get (void)
return full_i ();
{
}
// Use the Scoped Locking idiom to
// acquire/release the <monitor_lock_> upon
These methods illustrate a simple example of the Thread-
// entry/exit to the synchronized method.
Guard<Thread_Mutex> guard (monitor_lock_);
safe Interface idiom outlined above. They use the Scoped
Locking idiom [6] to acquire/release the monitor lock and
// Wait while the queue is empty.
then immediately forward to the corresponding implemen-
while (empty_i ()) {
tation method. As shown next, these methods assume the
// Release <monitor_lock_> and wait for a new
6
// <Message> to be placed in the queue.
The
// <monitor_lock_> is reacquired automatically
// when <wait> returns.
Supplier
Consumer
not_empty_.wait ();
Routing
}
Handler
Table
Handler
Message Queue
// Dequeue the first <Message> in the queue
2: find (msg)
// and update the <message_count_>.
Message m = get_i ();
Consumer
Supplier 3: put (msg)
Handler
// Notify any thread waiting in <put> that the
Handler
// queue has room for at least one <Message>.
Message Queue
not_full_.notify ();
4: get (msg)
1: recv (msg)
5: send (msg)
return m;
// Destructor of <guard> releases <monitor_lock_>.
}
INCOMING
OUTGOING
INCOMING
GATEWAY MESSAGES
MESSAGES
MESSAGES
OUTGOING
As before, note how the get synchronized method focuses
MESSAGES
on the synchronization and scheduling logic, while forward-
ing the actual dequeueing operation to the get i method.
SUPPLIER
CONSUMER
CONSUMER
SUPPLIER
10
Example Resolved
Figure 3: Implementing the Communication Gateway Using
the Monitor Object Pattern
The Gateway application can use the Monitor Object pattern
to implement a thread-safe message queue that decouples
supplier handler and consumer handler threads so they run
which is used as a key into a routing table that maps keys
concurrently and block independently. Embedding and au-
to Consumer Handlers. Each Consumer Handler is
tomating synchronization inside message queue monitor ob-
responsible for receiving messages from suppliers via its
jects protects their internal state from corruption and shields
put method and storing each message its Message Queue
clients from low-level synchronization concerns.
monitor object, as follows:
Internally, the Gateway contains Supplier Handler
and Consumer Handler objects that act as local prox-
Supplier_Handler::route_message (const Message &msg)
{
ies [7, 8] for remote suppliers and consumers, respectively.
// Locate the appropriate consumer based on the
Each Consumer Handler contains a Message Queue
// address information in the <Message>.
object implemented using the Monitor Object pattern
Consumer_Handler *ch =
routing_table_.find (msg.address ());
as described in the Implementation section.
The
Consumer Handler class is defined as follows:
// Put the <Message> into the <Consumer Handler>,
// which will store it in its <Message Queue>
class Consumer_Handler
// monitor object.
{
ch->put (msg);
public:
};
Consumer_Handler (void);
Each Consumer Handler spawns a separate thread of
// Put the message into the queue
control in its constructor to process the messages placed into
// monitor object, blocking until
// there’s room in the queue.
its message queue, as follows:
void put (const Message &msg) {
message_queue_.put (msg);
Consumer_Handler::Consumer_Handler (void)
}
{
// Spawn a separate thread to get messages
private:
// from the message queue and send them to
// Message queue implemented as a
// the remote consumer via TCP.
// monitor object.
Thread_Manager::instance ()->spawn (svc_run,
Message_Queue message_queue_;
this);
}
// Connection to the remote consumer.
SOCK_Stream connection_;
Each Consumer Handler thread executes the svc run
// Entry point into a new consumer
method, which gets the messages placed into the queue by
// handler thread.
Supplier Handler threads and sends them over its TCP
static void *svc_run (void *arg);
connection to the remote consumer, as follows:
};
void *
As shown in Figure 3, each Supplier Handler runs
Consumer_Handler::svc_run (void *args)
in its own thread, receive messages from its remote supplier,
{
Consumer_Handler *this_obj =
and routes the messages to their remote consumers. Routing
reinterpret_cast<Consumer_Handler *> (args);
is performed by inspecting an address field in each message,
7
for (;;) {
The following illustrates how the put method can
// This thread blocks on <get> until the
be implemented using the timed wait feature of the
// next <Message> is available.
Thread Condition condition variable wrapper outlined
Message msg =
this_obj->message_queue_.get ();
in the Implementation section:
// Transmit message to the consumer.
void
this_obj->connection_.send (msg,
Message_Queue::put (const Message &msg,
msg.length ());
Time_Value *timeout)
}
throw (Timedout)
}
{
// ... Same as before ...
The Message Queue is implemented as a monitor ob-
// Wait while the queue is full.
ject. Therefore, the send operation on the connection
can block in a Consumer Handler without affecting
while (full_i ()) {
the quality of service of other
// Release <monitor_lock_> and suspend our
Consumer Handlers or
// thread waiting for space to become available
Supplier Handlers.
// in the queue or for <timeout> to elapse.
// The <monitor_lock_> is reacquired automatically
// when <wait> returns, regardless of whether
11
Variants
// a timeout occurred or not.
if (not_full_.wait (timeout) == -1
&& errno = ETIMEDOUT)
The following are variations of the Monitor Object pattern.
throw Timedout ();
}
Timed synchronized method invocations:
Many applica-
// ... Same as before ...
tions can benefit from timed synchronized method invoca-
}
tions. Timed invocations enable clients to bound the amount
of time they are willing to wait for a synchronized method to
Strategized locking:
The Strategized Locking pattern can
enter its monitor object’s critical section.
be applied to make a monitor object more flexible and
The Message Queue monitor object interface defined
reusable.
earlier can be modified to support timed synchronized
For instance, the following template class parameterizes
method invocations, as follows:
the synchronization aspects of a Message Queue:
class Message_Queue
template <class SYNCH_STRATEGY>
{
class Message_Queue
public:
{
// = Message queue synchronized methods.
// ...
// Put the <Message> at the tail of the queue.
private:
// If the queue is full, block until the queue
typename SYNCH_STRATEGY::MUTEX monitor_lock_;
// is not full.
If <timeout> is 0 then block
typename SYNCH_STRATEGY::CONDITION not_empty_;
// until the <Message> is inserted into the queue.
typename SYNCH_STRATEGY::CONDITION not_full_;
// Otherwise, if <timeout> expires before the
// ...
// <Message> is enqueued, the <Timedout> exception
};
// is thrown.
void put (const Message &msg,
Time_Value *timeout = 0)
Each synchronized method is then modified as shown by the
throw (Timedout);
following empty method:
// Get the <Message> at the head of the queue.
template <class SYNCH_STRATEGY> bool
// If the queue is empty, block until the queue
Message_Queue<SYNCH_STRATEGY::empty (void) const
// is not empty.
If <timeout> is 0 then block
{
// until the <Message> is inserted into the queue.
Guard<SYNCH_STRATEGY::MUTEX> guard (monitor_lock_);
// Otherwise, if <timeout> expires before the
return empty_i ();
// <Message> is enqueued, the <Timedout> exception
}
// is thrown.
Message get (Time_Value *timeout = 0)
To parameterize the synchronization aspects associated
throw (Timedout);
with a Message Queue, we can define a pair of classes,
// ...
MT SYNCH and NULL SYNCH, that typedef the appropriate
};
C++ traits, as follows:
If timeout is 0 then both get and put will block indefi-
class MT_SYNCH {
public:
nitely until a Message is either removed or inserted into a
// Synchronization traits.
Message Queue monitor object, respectively. Otherwise,
typedef Thread_Mutex MUTEX;
if the timeout period expires, the Timedout exception is
typedef Thread_Condition CONDITION;
};
thrown and the client must be prepared to handle this excep-
tion.
class NULL_SYNCH {
8
// Synchronization traits.
Simplify synchronization of methods invoked concur-
typedef Null_Mutex MUTEX;
rently on an object:
Clients need not be concerned with
typedef Null_Thread_Condition CONDITION;
concurrency control when invoking methods on a monitor
};
object. If a programming language doesn’t support monitor
Thus, to define a thread-safe Message Queue, we just pa-
objects as a language feature, developers can use idioms like
rameterize it with the MT SYNCH strategy, as follows:
Scoped Locking [6] to simplify and automate the acquisition
and release of monitor locks that serializes access to internal
Message_Queue<MT_SYNCH> message_queue;
monitor object methods and state.
Likewise, to create a non-thread-safe Message Queue, we
Synchronized methods can cooperatively schedule their
can simply parameterize it with the following NULL SYNCH
order of execution:
Synchronized methods use their mon-
strategy.
itor conditions to determine the circumstances under which
they should suspend or resume their execution. For instance,
Message_Queue<NULL_SYNCH> message_queue;
methods can suspend themselves and wait to be notified
when arbitrarily complex conditions occur without using in-
Note that when using the Strategized Locking pattern in
efficient polling. This feature makes it possible for monitor
C++, it may not be possible for the component class to know
objects to cooperatively schedule their methods in separate
what type of synchronization strategy will be configured for
threads.
a particular use case. Therefore, it is important to apply
the Thread-safe Interface idiom to ensure that intra-object
However, the Monitor Object pattern has the following lia-
method calls, such as put calling full and put i, avoid
bilities:
self-deadlock and/or minimize recursive locking overhead.
Tightly coupling between object functionality and syn-
chronization mechanisms:
It is usually straightforward to
decouple an active object’s functionality from its synchro-
12
Known Uses
nization policies because it has a separate scheduler.
In
contrast, a monitor object’s synchronization and scheduling
The following are some known uses of the Monitor Object
logic is often closely coupled with its methods’ functionality.
pattern:
Although this makes monitor objects more efficient than ac-
Dijkstra/Hoare monitors:
Dijkstra [9] and Hoare [5] de-
tive objects, it may be hard to change their synchronization
fined programming language features called monitors to en-
policies or mechanisms without directly changing the moni-
capsulate service functions and their internal variables into
tor object’s method implementations. One way to reduce the
thread-safe modules. To prevent race conditions, a monitor
coupling of synchronization and functionality in monitor ob-
contains a lock that allows only one function at a time to be
jects is to use Aspect-Oriented Programming, as described
active within the monitor. Functions that want to temporar-
in Thread-Safe Interface idiom and Strategized Locking pat-
ily leave the monitor can block on a condition variable. It
tern [6].
is the responsibility of the programming language compiler
Nested monitor lockout:
This problem can occur when a
to generate run-time code that implements and manages the
monitor object is nested within another monitor object. For
monitor lock and condition variables.
instance, consider the following two Java classes:
Java Objects:
The main synchronization mechanism in
class Inner {
Java is based on Dijkstra/Hoare-style monitors. Each Java
protected boolean cond_ = false;
object is implicitly a monitor that internally contains a mon-
itor lock and a single monitor condition. Java’s monitors are
public synchronized void awaitCondition () {
while (!cond)
relatively simple, i.e., they allow threads to (1) implicitly se-
try { wait (); }
rialize their execution via method-call interfaces and (2) to
catch (InterruptedException e) {}
coordinate their activities via explicit wait, notify, and
// Any other code.
}
notifyAll operations.
public synchronized
ACE Gateway:
The example from Section 10 is based on
void notifyCondition (boolean c) {
a communication Gateway [10] from the ACE framework.
cond_ = c;
The Message Queues used by Consumer Handlers
notifyAll ();
}
in the Gateway are reusable ACE components implemented
}
as monitor objects. The Monitor Object pattern is used in
the ACE Gateway to simplify concurrent programming and
class Outer {
protected Inner inner_ =
improve performance on multi-processors.
new Inner ();
public synchronized void process () {
13
Consequences
inner_.awaitCondition ();
}
The Monitor Object pattern provides the following benefits:
9
public synchronized
Acknowledgements
void set (boolean c) {
inner_.notifyCondition (c);
}
References
}
[1] R. G. Lavender and D. C. Schmidt, “Active Object: an Object Be-
havioral Pattern for Concurrent Programming,” in Pattern Languages
The code above illustrates the canonical form of the the
of Program Design (J. O. Coplien, J. Vlissides, and N. Kerth, eds.),
Reading, MA: Addison-Wesley, 1996.
nested monitor lockout problem in Java.
When a Java
[2] W. R. Stevens, TCP/IP Illustrated, Volume 1. Reading, Massachusetts:
thread blocks in the monitor’s wait queue, all its locks are
Addison Wesley, 1993.
held except the lock of the object placed in the queue.
[3] D. C. Schmidt, “Wrapper Facade: A Structural Pattern for Encapsulat-
Consider what would happen if thread
made a call to
ing Functions within Classes,” C++ Report, vol. 11, February 1999.
T
1
Outer.process and as a result blocked in the wait call
[4] IEEE, Threads Extension for Portable Operating Systems (Draft 10),
in Inner.awaitCondition. In Java, the Inner and
February 1996.
Outer classes do not share their monitor locks. Thus, the
[5] C. A. R. Hoare, “Monitors: An Operating System Structuring Mecha-
nism,” Communications of the ACM, vol. 17, Oct. 1974.
awaitCondition call would release the Inner moni-
[6] D. C. Schmidt, “Strategized Locking, Thread-safe Decorator, and
tor, while retaining the Outer monitor. However, another
Scoped Locking: Patterns and Idioms for Simplifying Multi-threaded
thread,
cannot acquire the Outer monitor because it is
C++ Components,” C++ Report, vol. 11, Sept. 1999.
T
2
locked by the synchronized process method. As a result,
[7] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal,
the Outer.set condition cannot become true and
will
Pattern-Oriented Software Architecture - A System of Patterns. Wiley
T
1
and Sons, 1996.
continue to block in wait forever. Techniques for avoiding
[8] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Pat-
nested monitor lockout in Java are described in [11, 12].
terns: Elements of Reusable Object-Oriented Software. Reading, MA:
Addison-Wesley, 1995.
[9] E. W. Dijkstra, “Cooperating Sequential Processes,” in Programming
Languages (F. Genuys, ed.), Reading, MA: Academic Press, 1968.
14
See Also
[10] D. C. Schmidt, “A Family of Design Patterns for Application-level
Gateways,” The Theory and Practice of Object Systems (Special Issue
on Patterns and Pattern Languages), vol. 2, no. 1, 1996.
The Monitor Object pattern has several properties in com-
[11] D. Lea, Concurrent Java: Design Principles and Patterns. Reading,
mon with the Active Object pattern [1]. For instance, both
MA: Addison-Wesley, 1996.
patterns can be used to synchronize and schedule methods
[12] P. Jain and D. Schmidt, “Experiences Converting a C++ Communica-
invoked concurrently on an object. One difference is that an
tion Software Framework to Java,” C++ Report, vol. 9, January 1997.
active object executes its methods in a different thread than
its client(s), whereas a monitor object executes its methods
in its client threads. As a result, active objects can perform
more sophisticated, albeit more expensive, scheduling to de-
termine the order in which their methods execute. Another
difference is that monitor objects typically couple their syn-
chronization logic more closely with their methods’ func-
tionality. In contrast, it is easier to decouple an active ob-
ject’s functionality from its synchronization policies because
it has a separate scheduler.
For example, it is instructive to compare the Monitor Ob-
ject solution in Section 10 with the solution presented in
the Active Object [1] pattern.
Both solutions have sim-
ilar overall application architectures.
In particular, the
Supplier Handler and Consumer Handler imple-
mentations are almost identical. The primary difference is
that the Message Queue itself is easier to program and is
more efficient when it’s implemented using the Monitor Ob-
ject pattern rather than the Active Object pattern.
If a more sophisticated queueing strategy was necessary,
however, the Active Object pattern might be more appro-
priate. Likewise, because active objects execute in differ-
ent threads than their clients, there are use cases where ac-
tive objects can improve overall application concurrency by
executing multiple operations asynchronously. When these
operations are complete, clients can obtain their results via
futures.
10