Object » Monitor
A monitor provides process synchronization that is more high level than the one provided by a Semaphore. Similar to the classical definition of a Monitor it has the following properties:
mutex - Semaphore
This semaphore is used to ensure that at most one process can enter the critical section.
queuesMutex - Semaphore
This semaphore is used to implement mutual access to the queues.
nestingLevel - Integer
A positive value says how many times the ownerProcess has to exit the
monitor before a different process may enter.
ownerProcess - Process
The process that is currently in possession of the monitor or nil
if no process is currently possessing the monitor.
defaultQueue - OrderedCollection
This collection contains semaphores that are used to suspend processes
that cannot immediately continue. Processes that are waiting for the
default event are queued in this collection.
queueDict - IdentityDictionary
This dictionary associates event names with collections that contain
semaphores. Each semaphore is used to suspend one process that has
to wait for a signal that allows continuation.
critical: aBlock
Evaluates aBlock as a critical section and answers the evaluated result.
At any time, only one process can execute code in a critical section.
wait
Unconditional waiting for the default event.
The current process gets blocked and leaves the monitor, which means
that the monitor allows another process to execute critical code.
When the default event is signaled, the original process is resumed.
waitWhile: aBlock
Conditional waiting for the default event.
The current process gets blocked and leaves the monitor only if the
argument block evaluates to true. This means that another process
can enter the monitor. When the default event is signaled, the
original process is resumed, which means that the condition
(argument block) is checked again. Only if it evaluates to false,
does execution proceed. Otherwise, the process gets blocked and
leaves the monitor again...
waitUntil: aBlock
Conditional waiting for the default event.
See Monitor>>waitWhile: aBlock.
signal
One process waiting for the default event is woken up.
signalAll
All processes waiting for the default event are woken up.
The first example demonstrates the transmission of data from a producer to a consumer.
| monitor producer consumer isEmpty variable oc finished | monitor := Monitor new. isEmpty := true. variable := nil. finished := Semaphore new. oc := OrderedCollection new. producer := [ 1 to: 10 do: [:i | monitor critical: [isEmpty ifFalse: [monitor wait]. variable := 2*i. isEmpty := false. monitor signal ] ]. finished signal. ]. consumer := [ 1 to: 10 do: [:j | monitor critical: [isEmpty ifTrue: [monitor wait]. oc add: variable. isEmpty := true. monitor signal. ] ]. finished signal. ]. producer forkAt: Processor userBackgroundPriority. consumer forkAt: Processor userBackgroundPriority. finished wait; wait. oc inspect
The second example is a reworked version of the first example. The reworked example demonstrates the use of conditional waits.
| monitor producer consumer isEmpty variable oc finished | monitor := Monitor new. isEmpty := true. variable := nil. finished := Semaphore new. oc := OrderedCollection new. producer := [ 1 to: 10 do: [:i | monitor critical: [monitor waitUntil: [isEmpty]. variable := 2*i. isEmpty := false. monitor signal. ] ]. finished signal. ]. consumer := [ 1 to: 10 do: [:j | monitor critical: [monitor waitWhile: [isEmpty]. oc add: variable. isEmpty := true. monitor signal. ] ]. finished signal. ]. producer forkAt: Processor userBackgroundPriority. consumer forkAt: Processor userBackgroundPriority. finished wait; wait. oc inspect
In the first and second example, the forking process uses a Semaphore to wait for the forked processes to notify their termination. In the third example, this semaphore is removed and the monitor is used to implement the waiting for process completion. To accomplish this, the example uses an additional counter variable, the named event #finish and a conditional wait statement:
| monitor producer consumer isEmpty variable oc users | monitor := Monitor new. isEmpty := true. variable := nil. oc := OrderedCollection new. users := 2. producer := [ 1 to: 10 do: [:i | monitor critical: [monitor waitUntil: [isEmpty]. variable := 2*i. isEmpty := false. monitor signal ] ]. monitor critical: [users := users - 1. monitor signal: #finish]. ]. consumer := [ 1 to: 10 do: [:j | monitor critical: [monitor waitWhile: [isEmpty]. oc add: variable. isEmpty := true. monitor signal] ]. monitor critical: [users := users - 1. monitor signal: #finish]. ]. producer forkAt: Processor userBackgroundPriority. consumer forkAt: Processor userBackgroundPriority. monitor critical: [monitor waitUntil: [users = 0] for: #finish. ]. oc inspect
It should be noted, that the same example can be programmed with critical sections that enclose all the activities of the processes:
| monitor producer consumer isEmpty variable oc users | monitor := Monitor new. isEmpty := true. variable := nil. oc := OrderedCollection new. users := 2. producer := [ monitor critical: [1 to: 10 do: [:i | monitor waitUntil: [isEmpty]. variable := 2*i. isEmpty := false. monitor signal ]. users := users - 1. monitor signal: #finish ]. ]. consumer := [ monitor critical: [1 to: 10 do: [:j | monitor waitWhile: [isEmpty]. oc add: variable. isEmpty := true. monitor signal. ]. users := users - 1. monitor signal: #finish. ]. ]. producer forkAt: Processor userBackgroundPriority. consumer forkAt: Processor userBackgroundPriority. monitor critical: [monitor waitUntil: [users = 0] for: #finish. ]. oc inspect