eXept Software AG Logo

Smalltalk/X Webserver

Documentation of class 'Semaphore':

Home

Documentation
www.exept.de
Everywhere
for:
[back]

Class: Semaphore


Inheritance:

   Object
   |
   +--AbstractLock
      |
      +--Semaphore
         |
         +--EventSemaphore
         |
         +--RecursionLock

Package:
stx:libbasic
Category:
Kernel-Processes
Version:
rev: 1.146 date: 2023/12/05 16:11:05
user: cg
file: Semaphore.st directory: libbasic
module: stx stc-classLibrary: libbasic

Description:


Semaphores are used to synchronize processes providing a nonBusy wait
mechanism. A process can wait for the availability of some resource by
performing a Semaphore>>wait, which will suspend the process until the
resource becomes available. Signalling is done by (another process performing)
Semaphore>>signal.
If the resource has been already available before the wait, no suspending is
done, but the resource immediately allocated.
The resource internally keeps a count, the number of times the resource can be
allocated. If the semaphore is created with a count greater than 1, the sema
can be waited-upon that many times without blocking.
On the other hand, if initialized with a negative count, the semaphore
must be signalled that many times more often in order for a wait to not block.
In other words: whenever the semaphore has a count greater than zero,
the wait operation will proceed. Otherwise, it will block until the count has
been incremented by signal operations to be greater than zero.

There are also semaphores for mutual access to a critical region
(Semaphore>>forMutualExclusion and Semaphore>>critical:).

Additional protocol is provided for oneShot semaphores,
(#signalOnce) and for conditional signalling (#signalIf).

You can also attach semaphores to external events (such as I/O arrival or
timer events).
This is done by telling the Processor to signal the semaphore
under some condition.
See 'Processor>>signal:afterSeconds:', 'Processor>>signal:onInput:' etc.

See examples in doc/coding (found in the CodingExamples-nameSpace).

Warning/Note/Hint:
    a Semaphore-forMutualExclusion does NEVER allow for the critical
    region to be entered twice - NOT EVEN by the same process.
    That means, that a recursive attempt to enter that section leads
    to a deadlock.
    Use a RecursionLock instead, to avoid this.

Hint:
    now (Jul2002), Semaphores now allow for a negative count; this allows for
    a sync-point to be implemented easily (i.e. to wait for multiple other processes
    to arrive at a sync-point).
    See examples.


[instance variables:]
    count                   <SmallInteger>          the number of waits, that will go through
                                                    without blocking.
                                                    Incremented on #signal; decremented on #wait.

    waitingProcesses        <OrderedCollection>     waiting processes - will be served first
                                                    come first served when signalled.

    lastOwnerId             <SmallInteger>          a debugging aid: set when count drops
                                                    to zero to the current processes id.
                                                    Helps in finding deadlocks.

    name                    <String>                a debugging aid: an optional userFriendly
                                                    name; helps to identify a semaphore easier.

copyright

COPYRIGHT (c) 1993 by Claus Gittinger All Rights Reserved This software is furnished under a license and may be used only in accordance with the terms of that license and with the inclusion of the above copyright notice. This software may not be provided or otherwise made available to, or used by, any other person. No title to or ownership of the software is hereby transferred.

Class protocol:

instance creation
o  cleanup
an emergency helper: manually signal all semaphores which were held by a now dead process.
Can only (;-?) happen, if a semaphore-holding process was hard terminated
(i.e. no ensure handling happened), and semas remain in a bad state.

o  forMutualExclusion
create & return a new semaphore which allows exactly one process to
wait on it without blocking. This type of semaphore is used
for mutual exclusion from critical regions (see #critical:).
Also see RecursionLock, to avoid deadlock in case of recursive entered
critical regions.

o  name: aString
create & return a new semaphore which blocks until a signal is sent

o  name: aString owner: owningObject
create & return a new semaphore which blocks until a signal is sent

o  new
create & return a new semaphore which blocks until a signal is sent

o  new: n
create & return a new semaphore which allows n waits before blocking


Instance protocol:

Compatibility-Squeak
o  isSignaled

o  waitTimeoutMSecs: milliSeconds

o  waitTimeoutSeconds: seconds
wait for the semaphore, but abort the wait after some time (seconds).
return the receiver if the semaphore triggered normal,
or nil if we return due to a timeout.

accessing
o  name: aString owner: anObject
an optional reference to someone who owns this semaphore,
typically a shared queue or a windowgroup or similar.
This has no semantic meaning and is only used to support debugging

o  owner
an optional reference to someone who owns this semaphore,
typically a shared queue or a windowgroup or similar.
This has no semantic meaning and is only used to support debugging

o  owner: anObject
an optional reference to someone who owns this semaphore,
typically a shared queue or a windowgroup or similar.
This has no semantic meaning and is only used to support debugging

o  synchronizationSemaphore
I am my own synchronization Object

inspecting
o  inspectorExtraAttributes
( an extension from the stx:libtool package )
(comment from inherited method)
extra (pseudo instvar) entries to be shown in an inspector.
Answers a dictionary of aString -> aBlock.
aString is name of extra attribute and MUST start with minus ($-).
aBlock returns the object representing extra attribute

o  inspectorExtraMenuOperations
( an extension from the stx:libtool package )
(comment from inherited method)
extra operation-menu entries to be shown in an inspector.
Answers a collection of spec-entries containing:
aString is the label of the menu item.
aBlock is evaluated when the menu item is selected.
[optional] boolean or boolean valueHolder if enabled.
To be redefined in objects which think that it makes sense to offer
often used operations in an inspector's top menu.
See SerialPort, Color or FileStream as examples.

printing & storing
o  displayOn: aGCOrStream
return a string to display the receiver - include the
count for your convenience

private-accessing
o  clear
clear the semaphore's count

o  initSignals
set the count of the semaphore to zero.
provided for ST-80 compatibility.

o  setCount: n
set the count of the semaphore;
that's the number of possible waits, without blocking

o  setCount: n name: aString
set the count of the semaphore;
that's the number of possible waits, without blocking.
The userFriendly name is only for debugging; it is shown eg. in the semaphore monitor

o  setCount: n name: aString owner: owningObject
set the count of the semaphore;
that's the number of possible waits, without blocking.
The userFriendly name is only for debugging; it is shown eg. in the semaphore monitor

queries
o  count
return the number of 'already-counted' trigger events.
That's the number of waits which will succeed without blocking

o  lastOwner
return the last owning process or nil
(the one which counted to zero).
May be very useful in debugging deadLock situations

o  lastOwnerId
return the processId of the last owning process
(the one which counted to zero).
May be very useful in debugging deadLock situations

o  numberOfWaitingProcesses
return the number of processes waiting on the receiver

semaphoreSet interface
o  checkAndAddWaitingProcess: process
interface for SemaphoreSet.
If the semaphore is available, decrement it and return true.
Otherwise register our process to be wakened up once the semaphore is available
and return false.
ATTENTION: this must be invoked with OperatingSystem-interrupts-blocked.

signaling
o  signal
waking up the highest prio waiter.

o  signal: anInteger
increment semaphore by anInteger waking up the highest prio waiters.

o  signalForAll
signal the semaphore for all waiters.
This can be used for process synchronization, if multiple processes are
waiting for a common event.

o  signalIf
signal the semaphore, but only if being waited upon.
This can be used for one-shot semaphores (i.e. not remembering
previous signals)

o  signalOnce
wakeup waiters - but only once.
I.e. if the semaphore has already been signaled, this is ignored.

signaling-private
o  signalIfWithoutReschedule
signal the semaphore, but only if being waited upon.
This can be used for one-shot semaphores (i.e. not remembering
previous signals).
Do not reschedule higher priority processes that may have become runnable now, but
answer true if a reschedule is needed, false if not.

o  signalOnceWithoutReschedule
wakeup waiters - but only once.
I.e. if the semaphore has already been signaled, this is ignored.

testing
o  wouldBlock
return true, if the receiver would block the activeProcess
if a wait was performed. False otherwise.
Attention: if asked without some global lock (blockedInterrupts),
the returned value may be outdated right away.

waiting
o  consume
consume the resource without waiting.
This works even if the count is 0 (count may become negative).
Answer the new count afterwards

o  consume: n
consume the resource n times without waiting.
This works even if the count is 0 (count may become negative).
Answer the new count afterwards

o  consumeIfPossible
if the semaphore is currently free,
acquire it, lock it and return true.
Otherwise, do not wait, but return false immediately.

o  critical: aBlock
evaluate aBlock as a critical region; the receiver must be
created using Semaphore>>forMutualExclusion

Usage example(s):

      the example below is stupid (it should use a SharedQueue,
      or at least a Queue with critical regions).
      Anyhow, it demonstrates how two processes lock each other
      from accessing coll at the same time

     |sema coll|

     sema := Semaphore forMutualExclusion.
     coll := OrderedCollection new:10.

     [
	1 to:1000 do:[:i |
	    sema critical:[
		coll addLast:i.
		(Delay forSeconds:0.1) wait.
	    ]
	]
     ] forkAt:4.

     [
	1 to:1000 do:[:i |
	    sema critical:[
		coll removeFirst.
		(Delay forSeconds:0.1) wait.
	    ]
	]
     ] forkAt:4.

o  critical: aBlock ifBlocking: blockingBlock
like critical:, but do not block if the lock cannot be acquired.
Instead, return the value of the second argument, blockingBlock.

o  critical: aBlock timeoutMs: timeoutMs ifBlocking: blockingBlock
like critical:, but do not block if the lock cannot be acquired
within timeoutMs milliseconds.
Instead, return the value of blockingBlock.

o  wait
wait for the semaphore

Usage example(s):

	 need a while-loop here, since more than one process may
	 wait for it and another one may also wake up.
	 Thus, the count is not always non-zero after returning from
	 suspend.

Usage example(s):

	     for some more descriptive info in processMonitor ...
	     ... set the state to #wait (instead of #suspend)

o  waitUncounted
wait for the semaphore; do not consume the resource
(i.e. do not count down)

o  waitUncountedWithTimeout: secondsOrNilOrTimeDuration
wait for the semaphore, but abort the wait after some time (seconds).
return the receiver if the semaphore triggered normal, nil if we return
due to a timeout.
The argument may be a time duration or the number of seconds as integer
or float (i.e. use 0.1 for a 100ms timeout).
With zero timeout, this can be used to poll a semaphore (returning
the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If seconds is nil, wait without timeout.

o  waitUncountedWithTimeoutMs: milliSecondsOrNil
wait for the semaphore; do not consume the resource
(i.e. do not count down).
Abort the wait after some time.
return the receiver if the semaphore triggered normal, nil if we return
due to a timeout.
With zero timeout, this can be used to poll a semaphore (returning
the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If milliSecondsOrNil is nil, wait without timeout.

o  waitUncountedWithTimeoutMs: milliSecondsOrNil state: newStateSymbol
wait for the semaphore; do not consume the resource
(i.e. do not count down).
Abort the wait after some time.
return the receiver if the semaphore triggered normal, nil if we return
due to a timeout.
With zero timeout, this can be used to poll a semaphore
(returning the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If milliSecondsOrNil is nil, wait without timeout.
The stateSymbol argument is purely for the ProcessMonitor, to present a nicer
threadState (#wait instead of #suspend)

o  waitWithTimeout: secondsOrNilOrTimeDuration
wait for the semaphore, but abort the wait after some time (seconds).
return the receiver if the semaphore triggered normal,
or nil if we return due to a timeout.

The argument may be a time duration or the number of seconds as integer
or float (i.e. use 0.1 for a 100ms timeout).
With zero timeout, this can be used to poll a semaphore (returning
the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If the argument is nil, wait without timeout (forever).

o  waitWithTimeoutMs: milliSecondsOrNil
wait for the semaphore, but abort the wait after some time.
return the receiver if the semaphore triggered normal, nil if we return
due to a timeout.
With zero timeout, this can be used to poll a semaphore (returning
the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If milliSeconds is nil, wait without timeout.

o  waitWithTimeoutMs: milliSecondsOrNil state: waitStateSymbol
wait for the semaphore, but abort the wait after some time.
return
the receiver if the semaphore triggered normal,
nil if we return due to a timeout.
With zero timeout, this can be used to poll a semaphore (returning
the receiver if the semaphore is available, nil if not).
However, polling is not the intended use of semaphores, though.
If milliSecondsOrNil is nil, wait without timeout.
The waitStateSymbol argument is purely for the ProcessMonitor, to present a nicer
threadState (e.g. #wait instead of #suspend).


Examples:


two processes synchronizing on a sema:
    |sema thread1 thread2|

    sema := Semaphore new.

    thread1 := [
                    Transcript showCR:'here is thread 1; now waiting ...'.
                    sema wait.
                    Transcript showCR:'here is thread 1 again.'.
               ] newProcess.

    thread2 := [
                    Transcript showCR:'here is thread 2; delaying a bit ...'.
                    Delay waitForSeconds:5.
                    Transcript showCR:'here is thread 2 again; now signalling the sema'.
                    sema signal.
                    Transcript showCR:'here is thread 2 after the signalling.'.
              ] newProcess.

    thread1 priority:7.
    thread2 priority:6.

    thread1 resume.
    thread2 resume.
semaphore for critical regions:
    |accessLock|

    accessLock := Semaphore forMutualExclusion.

    [
        5 timesRepeat:[
            Delay waitForSeconds:2.
            accessLock critical:[
                Transcript showCR:'thread1 in critical region'.
                Delay waitForSeconds:1.
                Transcript showCR:'thread1 leaving critical region'.
            ].
        ]
    ] forkAt:5.

    [
        5 timesRepeat:[
            Delay waitForSeconds:1.
            accessLock critical:[
                Transcript showCR:'thread2 in critical region'.
                Delay waitForSeconds:2.
                Transcript showCR:'thread2 leaving critical region'.
            ].
        ]
    ] forkAt:4.
a deadlock due to recursive enter of a critical region:
    |accessLock block|

    accessLock := Semaphore forMutualExclusion.

    block := [:arg |
                Transcript showCR:'about to enter'.
                accessLock critical:[
                    Transcript showCR:'entered - doing action'.
                    arg value
                ].
                Transcript showCR:'left region'.
             ].

    block value:[].                 'this works'.
    block value:[block value:[] ].  'this deadlocks'.
Avoid the deadlock by using a RecursionLock instead:
    |accessLock block|

    accessLock := RecursionLock new.

    block := [:arg |
                Transcript showCR:'about to enter'.
                accessLock critical:[
                    Transcript showCR:'entered - doing action'.
                    arg value
                ].
                Transcript showCR:'left region'.
             ].

    block value:[].                 'this works'.
    block value:[block value:[] ].  'this deadlocks'.
Wait for multiple processes to arrive at a sync-point:
    |syncSema proceedSema thread1 thread2 thread3|

    syncSema := Semaphore new.
    syncSema setCount:(1-3).
    proceedSema := Semaphore new.

    thread1 := [
                    Transcript showCR:'here is thread 1; now busy ...'.
                    Delay waitForSeconds:(2 + (Random nextIntegerBetween:2 and:4)).
                    Transcript showCR:'here is thread 1 again - now syncing.'.
                    syncSema signal.
                    Transcript showCR:'thread 1 is waiting for all others...'.
                    proceedSema wait.
                    Transcript showCR:'thread 1 done.'.
               ] newProcess.

    thread2 := [
                    Transcript showCR:'here is thread 2; now busy ...'.
                    Delay waitForSeconds:(3 + (Random nextIntegerBetween:2 and:4)).
                    Transcript showCR:'here is thread 2 again - now syncing.'.
                    syncSema signal.
                    Transcript showCR:'thread 2 is waiting for all others...'.
                    proceedSema wait.
                    Transcript showCR:'thread 2 done.'.
              ] newProcess.

    thread3 := [
                    Transcript showCR:'here is thread 3; now busy ...'.
                    Delay waitForSeconds:(4 + (Random nextIntegerBetween:2 and:4)).
                    Transcript showCR:'here is thread 3 again - now syncing.'.
                    syncSema signal.
                    Transcript showCR:'thread 3 is waiting for all others...'.
                    proceedSema wait.
                    Transcript showCR:'thread 3 done.'.
              ] newProcess.

    thread1 priority:7.
    thread2 priority:6.
    thread3 priority:9.

    thread1 resume.
    thread2 resume.
    thread3 resume.

    Transcript showCR:'main thread: now waiting for other threads...'.
    syncSema wait.
    Transcript showCR:'main thread: all other threads at syncPoint.'.
    Delay waitForSeconds:2.
    Transcript showCR:'main thread: now let them proceed...'.
    proceedSema signalForAll.
    Transcript showCR:'main thread: done.'.
waitWithTimeout:0 can also be used to conditionally acquire the semaphore i.e. only acquire it if it is available. |s| s := Semaphore new. [ (s waitWithTimeout:0) notNil ifTrue:[ Transcript showCR:'process1 got the sema'. Delay waitForSeconds:1. Transcript showCR:'process1 signals sema'. s signal. ] ifFalse:[ Transcript showCR:'process1 has NOT got the sema'. ]. ] fork. [ (s waitWithTimeout:0) notNil ifTrue:[ Transcript showCR:'process2 got the sema'. Delay waitForSeconds:1. Transcript showCR:'process2 signals sema'. s signal. ] ifFalse:[ Transcript showCR:'process2 has NOT got the sema'. ] ] fork. s signal. Delay waitForSeconds:0.5. Transcript showCR:'master waits for sema'. s wait. Transcript showCR:'master got the sema'.

ST/X 7.7.0.0; WebServer 1.702 at 20f6060372b9.unknown:8081; Sat, 27 Jul 2024 07:06:02 GMT