Object » Collection » SequenceableCollection » LinkedList » Semaphore
A Semaphore provides synchronized communication of a single bit of information (a "signal") between Processes. A signal is sent by sending the message signal and received by sending the message wait. If no signal has been sent when a wait message is sent, the sending Process will be queued to wait for a signal that permits continuation.
A semaphore maintains a counter of excess signals and a queue of waiting processes. It stores and forwards permissions to continue process execution and it halts processes that need a permission that was still not given.
Semaphore is a subclass of LinkedList. The inherited functionality of LinkedList is used to queue waiting processes.
A semaphore lives in the Smalltalk image; it is not mapped to a semaphore of your computer's operating system. A primitive can have access to a semaphore that is registered in slot 39 of the special objects array (see the class protocol of class ExternalObjectsTable for details of doing that).
excessSignals - Integer
This variable counts the number of received signal messages that were
not consumed by corresponding wait messages.
new
Answers a new semaphore that has no excess signals.
forMutualExclusion
Answers a new semaphore that has one excess signal. This new
semaphore can immediately be used for mutual exclusion. (see
instance methods critical: and critical:ifError:)
size
The length of the queue. This value is equal to the number
of processes that are currently waiting for a permission to
continue execution.
wait
The active Process must receive a signal
through the receiver before proceeding. If no signal has been sent,
the active process will be queued until one is sent.
signal
Send a signal through the receiver. If one or more processes
have been queued for a signal, allow the first one to
proceed. If no process is waiting, remember the excess signal.
critical: mutuallyExcludedBlock
This method uses the reciever to implement a critical section. A critical
section is an environment that can be entered by at most one process.
Evaluate mutuallyExcludedBlock only if the receiver is not currently in
the process of running the critical: message. If the receiver is, evaluate
mutuallyExcludedBlock after the other critical: message is finished.
The critical section is left after complete evaluation of mutuallyExcludedBlock or when that block is prematurely left during stack unwinding. The use of ensure: ensures that the semaphore is signalled under all circumstances when the critical section is left.
critical: aBlock
ifError: errorBlock
This method enters the receiver to evaluate aBlock.
The method provides a special exception handling that ensures
that the critical region is left. The block errorBlock is
evaluated after the critical region was left.
Note for Squeak 3.7:
This method has a subtle bug: It does not signal the semaphore when
aBlock is prematurely left during stack unwinding. The use of
this method is therefore currently not recommended. You should better
implement robust exception handling in aBlock.
(more about that problem)
terminateProcess
Removes one process from the queue and terminates it. Does
nothing if the queue is empty.
A semaphore can be used to implement exclusive access to a variable:
| variable sema | sema := Semaphore forMutualExclusion. sema critical: [variable := <an expression>]. sema critical: [self use: variable].
Blocks that are provided as an argument to Semaphore>>critical: should have robust exception handling. For blocks that do not handle exceptions, the use of Semaphore>>critical:ifError: is recommended. That method ensures that exception handling occurs after mutual exclusion is left.
Mutual exclusion ensures that at most one process enters the critical region. It does not ensure that all expressions of the critical region are always evaluated. The following example demonstrates that
Object subclass: #TwoItems instanceVariableNames: 'firstItem secondItem' classVariableNames: '' poolDictionaries: '' category: 'Squeak Peculiarities'
initialize firstItem := secondItem := 0.
This class uses three simple methods to initialize its instance variables and to increment them:
incFirst firstItem := firstItem + 1.
incSecond secondItem := secondItem + 1.
Now we can write:
| protectedObject mutualExclusionSema yieldSema goon workerProcess terminator | mutualExclusionSema := Semaphore forMutualExclusion. yieldSema := Semaphore new. goon := Semaphore new. protectedObject := TwoItems new. workerProcess := [mutualExclusionSema critical: [protectedObject incFirst. yieldSema signal. protectedObject incSecond. ]. ] newProcess. terminator := [yieldSema wait. workerProcess terminate. goon signal.] newProcess. terminator priority: Processor timingPriority ; resume. workerProcess resume. goon wait. protectedObject inspect
We see that only one of the variables in the protected object was incremented.