Monitor Object
An Object Behavioral Pattern for Concurrent Programming
Douglas C. Schmidt [email protected] Department of Computer Science Washington University, St. Louis
1Intent the corresponding consumer handler, whose thread then de- livers the message to its remote consumer. 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 [1] 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 from its supplier handler threads. 3 Example One way to implement a thread-safe Message Queue is to use the Active Object pattern [2], which decouples the Consider the design of the communication Gateway shown thread used to invoke a method from the thread used to exe- in Figure 1. The Gateway process contains multiple supplier cute the method. As shown in Figure 2, each message queue 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) 3: put (msg) 1: put (msg) Handler Supplier 2: put (msg) Consumer Handler Message Queue Handler 4: get (msg) 5: send (msg) Supplier 1: recv (msg) Handler 3: get (msg) INCOMING OUTGOING 4: send (msg) INCOMING GATEWAY MESSAGES OUTGOING MESSAGES MESSAGES MESSAGES GATEWAY OUTGOING MESSAGES
Figure 2: Implementing Message Queues as Active Objects SUPPLIER CONSUMER SUPPLIER CONSUMER Figure 1: Communication Gateway the Active Object pattern to implement a thread-safe mes- sage queue decouples supplier handler threads in the Gate- handler and consumer handler objects that run in separate way process from consumer handler threads so all threads threads and route messages from one or more remote sup- can run concurrently and block independently when flow pliers to one or more remote consumers, respectively. When control occurs on various TCP connections. a supplier handler thread receives a message from a remote Although the Active Object pattern can be used to imple- supplier, it uses an address field in the message to determine ment a functional Gateway, it has the following drawbacks:
1 Performance overhead: The Active Object pattern pro- 3. Objects should be able to schedule their methods coop- vides a powerful concurrency model. It not only synchro- eratively: If an object’s methods must block during their nizes concurrent method requests on an object, but also can execution, they should be able to voluntarily relinquish their perform sophisticated scheduling decisions to determine the thread of control so that methods called from other client order in which requests execute. These features incur non- threads can access the object. This property helps prevent trivial amounts of context switching, synchronization, dy- deadlock and makes it possible to leverage the concurrency namic memory management, and data movement overhead, available on hardware/software platforms. however, when scheduling and executing method requests. Programming overhead: The Active Object pattern re- quires programmers to implement up to six components: 6 Solution proxies, method requests,anactivation queue,ascheduler,a For each object accessed concurrently by client threads de- servant,andfutures for each proxy method. Although some fine it as a monitor object. Clients can access the services components, such as activation queues and method requests, defined by a monitor object only through its synchronized can be reused, programmers may have to reimplement or sig- methods. To prevent race conditions involving monitor ob- nificantly customize these components each time they apply ject state, only one synchronized method at a time can run the pattern. within a monitor object. Each monitored object contains a In general, the performance and programming overhead monitor lock that synchronized methods use to serialize their outlined above can be unnecessarily expensive if an applica- access to an object’s behavior and state. In addition, syn- tion does not require all the Active Object pattern features, chronized methods can determine the circumstances under particularly its sophisticated scheduling support. Yet, pro- which they suspend and resume their execution based on one grammers of concurrent applications must ensure that certain or more monitor conditions associated with a monitor object. method requests on objects are synchronized and/or sched- 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 5Problem 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.1 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- ods in order to protect an object’s data from uncontrolled Synchronized methods implement the thread-safe ser- changes. It is relatively straightforward to extend this object- vices exported by a monitor object. To prevent race oriented programming model to protect an object’s data from conditions, only one synchronized method can execute uncontrolled concurrent changes, known as race conditions. within a monitor at any point in time, regardless of the Therefore, an object’s method interface should define its syn- number of threads that invoke the object’s synchronized chronization boundaries. methods concurrently or the number of synchronized methods in the object’s class. 2. Objects, not clients, should be responsible for their own method synchronization: Concurrent applications For instance, the put and get operations on the con- are harder to program if clients must explicitly acquire sumer handler’s message queue should be synchronized and release low-level synchronization mechanisms, such as methods to ensure that routing messages can be inserted semaphores, mutexes, or condition variables. Thus, objects and removed simultaneously by multiple threads with- should be responsible for ensuring that any of their methods out corrupting a queue’s internal state. requiring synchronization are serialized transparently, i.e., 1 In contrast, an active object [2] does have its own thread of control. without explicit client intervention.
2 Monitor lock 1. Synchronized method invocation and serialization: When a client invokes a synchronized method on a moni-
Each monitor object contains its own monitor lock. tor object, the method must first acquire its monitor lock. A Synchronized methods use this monitor lock to seri- monitor lock cannot be acquired as long as another synchro- alize method invocations on a per-object basis. Each nized method is executing within the monitor object. In this synchronized method must acquire/release the monitor case, the client thread will block until it acquires the monitor lock when the method enters/exits the object, respec- lock, at which point the synchronized method will acquire tively. This protocol ensures the monitor lock is held the lock, enter its critical section, and perform the service whenever a method performs operations that access or implemented by the method. Once the synchronized method modify its object’s state. has finished executing, the monitor lock must be released so For instance, a Thread Mutex [3]couldbeusedto that other synchronized methods can access the monitor ob- implement the message queue’s monitor lock. ject.
Monitor condition 2. Synchronized method thread suspension: If a syn- chronized method must block or cannot otherwise make im-
Multiple synchronized methods running in separate mediate progress, it can wait on one of its monitor condi- threads can cooperatively schedule their execution se- tions, which causes it to “leave” the monitor object temporar- quences by waiting for and notifying each other via ily [5]. When a synchronized method leaves the monitor monitor conditions associated with their monitor object. object, the monitor lock is released automatically and the Synchronized methods use monitor conditions to deter- client’s thread of control is suspended on the monitor con- mine the circumstances under which they should sus- dition. pend or resume their processing. For instance, when a consumer handler thread attempts 3. Method condition notification: A synchronized to dequeue a routing message from an empty message method can notify a monitor condition in order to resume a queue, the queue’s get method must release the mon- synchronized method’s thread that had previously suspended itor lock and suspend itself until queue is no longer itself on the monitor condition. In addition, a synchronized empty, i.e., when a supplier handler thread inserts a method can notify all other synchronized methods that pre- message in it. Likewise, when a supplier handler thread viously suspended their threads on a monitor condition. attempts to enqueue a message into a full queue, the queue’s put method must release the monitor lock and spend itself until the queue is no longer full, i.e.,when 4. Synchronized method thread resumption: Once a a consumer handler removes a message from it. A pair previously suspended synchronized method thread is noti- of POSIX condition variables [4] can be used to imple- fied, its execution can resume at the point where it waited ment the message queue’s not-empty and not-full moni- on the monitor condition. The monitor lock is automatically tor conditions. reacquired before the notified thread “reenters” the monitor object and resumes executing the synchronized method. The structure of the Monitor Object pattern is illustrated in the following UML class diagram: The following figure illustrates the collaborations in the Monitor Object pattern:
Monitor Object : client : client : monitor : monitor : monitor thread 1 thread 2 object lock condition synchronized_method_1() SYNCHRONIZED method_1() ... METHOD INVOCATION acquire() & SERIALIZATION synchronized_method_m() do_work() monitor_lock_ wait() SYNCHRONIZED METHOD monotor_condition_1_ THREAD SUSPENSION release() ... suspend thread 1 method_2() acquire() monitor_condition_n_ METHOD CONDITION NOTIFICATION do_work() notify() method_2() release() return resume thread 1 SYNCHRONIZED METHOD resume method_1() THREAD RESUMPTION acquire() do_work() 8 Dynamics 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,andfull 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
// Get the
4 A monitor lock can be implemented using a mutex.Amu- }; 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,andfull 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
// Internal Queue representation. // Notify one thread waiting on the ... //
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.Themutex lock is reacquired by Message_Queue::full_i (void) const the wait method before it returns to its client thread, e.g., { return message_count_ > max_messages_; either because the condition variable was notified or because } 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 // acquire/release the
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 Messagesat 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
6 //
SUPPLIER CONSUMER SUPPLIER CONSUMER 10 Example Resolved Figure 3: Implementing the Communication Gateway Using The Gateway application can use the Monitor Object pattern 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
7 for (;;) { The following illustrates how the put method can // This thread blocks on
// 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 ... Message Queue The 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 ()) { Consumer Handler // Release
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
// Put the
// Get the
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,wejustpa- 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
public synchronized void process () { inner_.awaitCondition (); 13 Consequences } The Monitor Object pattern provides the following benefits:
9 public synchronized Acknowledgements void set (boolean c) { inner_.notifyCondition (c); } References } [1] W.R.Stevens,TCP/IP Illustrated, Volume 1. Reading, Massachusetts: Addison Wesley, 1993. The code above illustrates the canonical form of the the [2] R. G. Lavender and D. C. Schmidt, “Active Object: an Object Be- nested monitor lockout problem in Java. When a Java havioral Pattern for Concurrent Programming,” in Pattern Languages of Program Design (J. O. Coplien, J. Vlissides, and N. Kerth, eds.), thread blocks in the monitor’s wait queue, all its locks are Reading, MA: Addison-Wesley, 1996. held except the lock of the object placed in the queue. [3] D. C. Schmidt, “Wrapper Facade: A Structural Pattern for Encapsulat-
T ing Functions within Classes,” C++ Report, vol. 11, February 1999.
Consider what would happen if thread 1 made a call to 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
T C++ Components,” C++ Report, vol. 11, Sept. 1999. thread, 2 cannot acquire the Outer monitor because it is locked by the synchronized process method. As a result, [7] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal,
Pattern-Oriented Software Architecture - A System of Patterns. Wiley T the Outer.set condition cannot become true and 1 will 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 [2]. 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 [2] 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