by Tamaki Kurusu
Monitors were first proposed by Brinch Hansen (1) and later refined by Hoare (2). Monitors provide a structured concurrent programming primitive, which is used by processes to ensure exclusive access to resources, and for synchronizing and communicating among users. A monitor module encapsulates both a resource definition and operations/ procedures that exclusively manipulate it. Those procedures are the gateway to the shared resource and called by the processes to access the resource. Only one call to a monitor procedure can be active at a time and this protects data inside the monitor from simultaneous access by multiple users. Thus mutual exclusion is enforced among tasks using a monitor. Processes that attempt monitor entry while the monitor is occupied are blocked on a monitor entry queue.
To synchronize tasks within the monitor, a condition variable is used to delay processes executing in a monitor. It may be declared only within a monitor and has no numeric value like semaphores do. Two operations, wait and signal, are defined on condition variables. The wait operation suspends/blocks execution of the calling process if a certain condition is true. Then the monitor is unlocked and allows another task to use the monitor. When the same condition becomes false, then the signal operation resumes execution of some process suspended after a wait on that condition, by placing it in the processor ready queue. If there are several such processes, choose one of them; if there are no waiting processes, the signal operator is ignored. Therefore, the introduction of condition variables allows more than one process to be in the same monitor at the same time, although only one of them will be actually active within that monitor.
A condition variable is associated with a queue of the processes that are currently waiting on that condition. First-in-first-out (FIFO) discipline is generally used with queues, but priority queues can also be implemented by specifying the priority of the process to be delayed as a parameter in the wait operation. (Condition variables are assumed to be fair in the sense that a process will not remain suspended forever on a condition variable that is signaled infinitely often.) Therefore, monitors allow flexibility in scheduling of the processes waiting in queues.
< Monitor-Name > : monitor
begin
Declaration of data local to the monitor.
.
.
procedure < Name > ( < formal parameters > );
begin
procedure body
end;
Declaration of other procedures
.
.
begin
Initialization of local data of the monitor
end;
end.
Note that a monitor is not a process, but a static module of data and procedure declarations. The actual processes which use the monitor need to be programmed separately. (3)
The wait and signal operations on condition variables in a monitor are similar to P and V operations on counting semaphores. A wait statement can block a process's execution, while a signal statement can cause another process to be unblocked. However, there are some differences between them. When a process executes a P operation, it does not necessarily block that process because the counting semaphore may be greater than zero. In contrast, when a wait statement is executed, it always blocks the process. When a task executes a V operation on a semaphore, it either unblocks a task waiting on that semaphore or increments the semaphore counter if there is no task to unlock. On the other hand, if a process executes a signal statement when there is no other process to unblock, there is no effect on the condition variable.
Another difference between semaphores and monitors is that users awaken by a V operation can resume execution without delay. Contrarily, users awaken by a signal operation are restarted only when the monitor is unlocked.
In addition, a monitor solution is more structured than the one with semaphores because the data and procedures are encapsulated in a single module and that the mutual exclusion is provided automatically by the implementation. (4)
An alternative to using condition variables for monitor synchronization is to use Automatic-signal monitors in which condition variables and signal statements are eliminated by modifying the wait statement to use a conditional expression (2):
wait conditional_expression
If the conditional expression is false, the user is blocked and the
monitor is unlocked. A waiting user is unblocked implicitly when the
expression it is waiting on becomes true.
Automatic signalling is accomplished by arranging for waiting users to wake up repeatedly (in round-robin fashion) to check their conditions. Hence, this could lead to a large amount of context- switching overhead if users remain blocked for long time. (4)
In Java, the following methods are used for a monitor implementation: wait, notify, and notifyAll.
When a wait method is invoked, the current thread is suspended. The Java run-time system places the thread in an internal wait queue associated with the target object. At the same time, the synchronization lock for the target object is released, but all other locks held by the thread are retained.
An invocation of the notify method results in the following: An arbitrarily chosen thread (T), if any exists, is removed from the internal wait queue associated with the target object. T must re- obtain the synchronization lock for the target object, which will always cause it to block at least until the thread calling notify releases the lock. It will continue to block if some other thread obtains the lock first.
A notifyAll method execution works in the same way as notify except that the steps occur for all threads waiting in the wait queue for the target object.
Two alternative versions of the wait method take arguments specifying the maximum waiting time in the wait queue. If a timed wait has not resumed before its time bound, notify is executed automatically.
Note that it is better to use notifyAll routinely rather than notify. Especially when attempting to create reusable classes and classes intended to be subclassed, one usually does not know enough about the context under which an object is operating to be absolutely sure that only one thread should be notified. Since one cannot specify which of the many threads that may be waiting are signaled by a given notify operation, notify can only be used when it is acceptable to resume any one of the threads that may be waiting and not to resume the others. (5)
Since only one process can be active within a monitor at a time, the absence of concurrency is the major weakness in monitors and this leads to weakening of encapsulation. For example, in the Readers-Writers problem, the protected resource (a file, database, etc.) is separated from the monitor and so, there is a possibility that some malicious Reader or Writer could simply access the database directly without getting a permission from the monitor to do so.
Another problem is the possibility of deadlock in the case of nested monitor calls. For example, consider a process calling a monitor that in turn calls another lower-level monitor procedure. If a wait is executed in the last monitor called, the mutual exclusion will be relinquished by the process. However, mutual exclusion will not be relinquished by processes in monitors from which nested calls have been made. Processes that attempt to invoke procedures in these monitors will become blocked. If those blocked processes are the only ones which can cause signaling to occur in the lower level monitor, then deadlock occurs. (3)
References: