allow for negative counts.
authorClaus Gittinger <cg@exept.de>
Wed, 10 Jul 2002 13:10:24 +0200
changeset 6615 2b56a43d0e89
parent 6614 bed21ae9a498
child 6616 70d54ffebc91
allow for negative counts.
Semaphore.st
--- a/Semaphore.st	Wed Jul 10 12:20:29 2002 +0200
+++ b/Semaphore.st	Wed Jul 10 13:10:24 2002 +0200
@@ -1,6 +1,6 @@
 "
  COPYRIGHT (c) 1993 by Claus Gittinger
-	      All Rights Reserved
+              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
@@ -13,10 +13,10 @@
 "{ Package: 'stx:libbasic' }"
 
 Object subclass:#Semaphore
-	instanceVariableNames:'count waitingProcesses lastOwnerID name'
-	classVariableNames:''
-	poolDictionaries:''
-	category:'Kernel-Processes'
+        instanceVariableNames:'count waitingProcesses lastOwnerID name'
+        classVariableNames:''
+        poolDictionaries:''
+        category:'Kernel-Processes'
 !
 
 !Semaphore class methodsFor:'documentation'!
@@ -24,7 +24,7 @@
 copyright
 "
  COPYRIGHT (c) 1993 by Claus Gittinger
-	      All Rights Reserved
+              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
@@ -44,6 +44,14 @@
     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:).
@@ -64,7 +72,13 @@
         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 of a semaphore to avoid this.
+        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:]
@@ -187,6 +201,62 @@
         block value:[].                 'this works'.
         block value:[block value:[] ].  'this deadlocks'.
                                                         [exEnd]
+
+
+   Wait for multiple processes to arrive at a sync-point:
+                                                        [exBegin]
+        |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.'.
+                                                        [exEnd]
 "
 ! !
 
@@ -223,25 +293,19 @@
     "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..
+     and return false.
+     ATTENTION: this must be invoked with OperatingSystem-interrupts-blocked.
     "
 
-    "
-     bad ST/X trick (needs change, when multiProcessor support is added):
-     this works only since interrupts are only serviced at 
-     message send and method-return time ....
-     If you add a message send into the ifTrue:-block, things will
-     go mad ... (especially be careful when adding a debugPrint-here)
-    "
-    count ~~ 0 ifTrue:[
-	count := count - 1.
-	count == 0 ifTrue:[
-	    lastOwnerID := Processor activeProcessId.
-	].
-	^ true
+    count > 0 ifTrue:[
+        count := count - 1.
+        count == 0 ifTrue:[
+            lastOwnerID := Processor activeProcessId.
+        ].
+        ^ true
     ].
     (waitingProcesses identityIndexOf:process) == 0 ifTrue:[
-	waitingProcesses add:process.
+        waitingProcesses add:process.
     ].
     ^ false
 
@@ -342,9 +406,11 @@
 
 wouldBlock
     "return true, if the receiver would block the activeProcess
-     if a wait was performed. False otherwise."
+     if a wait was performed. False otherwise.
+     Attention: if asked without some global lock (blockedInterrupts),
+     the returned value may be wrong right away."
 
-    ^ count == 0
+    ^ count <= 0
 ! !
 
 !Semaphore methodsFor:'testing'!
@@ -370,8 +436,18 @@
     "evaluate aBlock as a critical region; the receiver must be
      created using Semaphore>>forMutualExclusion"
 
-    self wait.
-    ^ aBlock ensure:[self signal].
+    |retVal gotSema|
+
+    [
+        gotSema := self wait.
+        retVal := aBlock value.
+    ] valueOnUnwindDo:[
+        "/ be careful - the unwind may occur both while waiting
+        "/ AND while evaluating the block.
+        gotSema notNil ifTrue:[self signal].
+    ].
+    self signal.
+    ^ retVal
 
     "
       the example below is stupid (it should use a SharedQueue,
@@ -485,21 +561,21 @@
 
 signalOnce
     "wakeup waiters - but only once.
-     I.e. if the semaphore has already been signalled, this
-     is ignored."
+     I.e. if the semaphore has already been signalled, this is ignored."
+
+    |wasBlocked p|
 
-    |wasBlocked|
-
-    count == 0 ifTrue:[
+    count <= 0 ifTrue:[
         wasBlocked := OperatingSystem blockInterrupts.
+        "/ check again - now interrupts are blocked.
         [
-            |p|
-
-            count == 0 ifTrue:[
-                count := 1.
-                p := waitingProcesses removeFirstIfAbsent:nil.
-                p notNil ifTrue:[
-                    Processor resume:p.
+            count <= 0 ifTrue:[
+                count := count + 1.
+                count == 1 ifTrue:[
+                    p := waitingProcesses removeFirstIfAbsent:nil.
+                    p notNil ifTrue:[
+                        Processor resume:p.
+                    ].
                 ].
             ].
         ] ensure:[
@@ -515,49 +591,38 @@
 
     |activeProcess wasBlocked|
 
-    "
-     bad ST/X trick (needs change, when multiProcessor support is added):
-     this works only since interrupts are only serviced at 
-     message send and method-return time ....
-     If you add a message send between the compare and the decrement,
-     things will go mad ... (especially be careful when adding a debugPrint-here)
-    "
-    count ~~ 0 ifTrue:[
-        count := count - 1.
-        count == 0 ifTrue:[
-            lastOwnerID := Processor activeProcessId.
+    wasBlocked := OperatingSystem blockInterrupts.
+
+    count <= 0 ifTrue:[
+        activeProcess := Processor activeProcess.
+
+        "
+         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.
+        "
+        [count <= 0] whileTrue:[
+            waitingProcesses add:activeProcess.
+            "
+             for some more descriptive info in processMonitor ...
+             ... set the state to #wait (instead of #suspend)
+            "
+            [
+                activeProcess suspendWithState:#wait
+            ] valueOnUnwindDo:[
+                waitingProcesses removeIdentical:activeProcess ifAbsent:[].
+                wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
+            ].
+            
+            count <= 0 ifTrue:[
+                "/ care for someone manually resuming me (i.e. semaphore still not avail)...
+                "/ being multiple times on waitingProcesses
+                waitingProcesses removeIdentical:activeProcess ifAbsent:[].
+            ]
         ].
-        ^ self
     ].
 
-    activeProcess := Processor activeProcess.
-
-    wasBlocked := OperatingSystem blockInterrupts.
-    "
-     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.
-    "
-    [count == 0] whileTrue:[
-        waitingProcesses add:activeProcess.
-        "
-         for some more descriptive info in processMonitor ...
-         ... set the state to #wait (instead of #suspend)
-        "
-        [
-            activeProcess suspendWithState:#wait
-        ] ifCurtailed:[
-            waitingProcesses removeIdentical:activeProcess ifAbsent:[].
-            wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
-        ].
-        
-        count == 0 ifTrue:[
-            "/ care for someone manually resuming me ...
-            "/ being multiple times on waitingProcesses
-            waitingProcesses removeIdentical:activeProcess ifAbsent:[].
-        ]
-    ].
     count := count - 1.
     count == 0 ifTrue:[
         lastOwnerID := Processor activeProcessId.
@@ -574,14 +639,7 @@
 
     |activeProcess wasBlocked|
 
-    "
-     bad ST/X trick (needs change, when multiProcessor support is added):
-     this works only since interrupts are only serviced at 
-     message send and method-return time ....
-     If you add a message send between the compare and the decrement,
-     things will go mad ... (especially be careful when adding a debugPrint-here)
-    "
-    count ~~ 0 ifTrue:[
+    count > 0 ifTrue:[
         ^ self
     ].
 
@@ -594,7 +652,7 @@
      Thus, the count is not always non-zero after returning from
      suspend.
     "
-    [count == 0] whileTrue:[
+    [count <= 0] whileTrue:[
         waitingProcesses add:activeProcess.
         "
          for some more descriptive info in processMonitor ...
@@ -602,12 +660,12 @@
         "
         [
             activeProcess suspendWithState:#wait
-        ] ifCurtailed:[
+        ] valueOnUnwindDo:[
             waitingProcesses removeIdentical:activeProcess ifAbsent:[].
             wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
         ].
-        count == 0 ifTrue:[
-            "/ care for someone manually resuming me ...
+        count <= 0 ifTrue:[
+            "/ care for someone manually resuming me (i.e. semaphore still not avail)...
             "/ being multiple times on waitingProcesses
             waitingProcesses removeIdentical:activeProcess ifAbsent:[].
         ]
@@ -649,76 +707,64 @@
 
     |activeProcess timeoutOccured wasBlocked unblock now endTime|
 
-    "
-     bad ST/X trick (needs change, when multiProcessor support is added):
-     this works only since interrupts are only serviced at 
-     message send and method-return time ....
-     If you add a message send between the compare and the decrement,
-     things will go mad ... (especially be careful when adding a debugPrint-here)
-    "
-    count ~~ 0 ifTrue:[
-        count := count - 1.
-        count == 0 ifTrue:[
-            lastOwnerID := Processor activeProcessId.
-        ].
-        ^ self
-    ].
-
-    "
-     with zero-timeout, this is a poll
-    "
-    milliSeconds = 0 ifTrue:[
-        ^ nil
-    ].
-
-    activeProcess := Processor activeProcess.
-
     wasBlocked := OperatingSystem blockInterrupts.
 
-    "
-     calculate the end-time
-    "
-    milliSeconds notNil ifTrue:[
-        now := OperatingSystem getMillisecondTime.
-        endTime := OperatingSystem millisecondTimeAdd:now and:milliSeconds.
-
-        unblock := [timeoutOccured := true. Processor resume:activeProcess].
-        Processor addTimedBlock:unblock for:activeProcess atMilliseconds:endTime.
-    ].
+    count <= 0 ifTrue:[
+        "
+         with zero-timeout, this is a poll
+        "
+        milliSeconds = 0 ifTrue:[
+            wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
+            ^ nil
+        ].
 
-    "
-     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.
-    "
-    [count == 0] whileTrue:[
-        waitingProcesses add:activeProcess.
+        activeProcess := Processor activeProcess.
 
-        timeoutOccured := false.
         "
-         for some more descriptive info in processMonitor ...
-         ... set the state to #wait (instead of #suspend)
+         calculate the end-time
         "
-        [
-            activeProcess suspendWithState:#wait.
-        ] ifCurtailed:[
-            unblock := nil.
-            waitingProcesses removeIdentical:activeProcess ifAbsent:[].
-            wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
+        milliSeconds notNil ifTrue:[
+            now := OperatingSystem getMillisecondTime.
+            endTime := OperatingSystem millisecondTimeAdd:now and:milliSeconds.
+
+            unblock := [timeoutOccured := true. Processor resume:activeProcess].
+            Processor addTimedBlock:unblock for:activeProcess atMilliseconds:endTime.
         ].
 
-        waitingProcesses removeIdentical:activeProcess ifAbsent:[].
-        timeoutOccured ifTrue:[
-            wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
-            unblock := nil.
-            ^ nil
+        "
+         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.
+        "
+        [count <= 0] whileTrue:[
+            waitingProcesses add:activeProcess.
+
+            timeoutOccured := false.
+            "
+             for some more descriptive info in processMonitor ...
+             ... set the state to #wait (instead of #suspend)
+            "
+            [
+                activeProcess suspendWithState:#wait.
+            ] valueOnUnwindDo:[
+                unblock := nil.
+                waitingProcesses removeIdentical:activeProcess ifAbsent:[].
+                wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
+            ].
+
+            waitingProcesses removeIdentical:activeProcess ifAbsent:[].
+            timeoutOccured ifTrue:[
+                wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
+                unblock := nil.
+                ^ nil
+            ].
         ].
-    ].
 
-    unblock notNil ifTrue:[
-        Processor removeTimedBlock:unblock.
-        unblock := nil.
+        unblock notNil ifTrue:[
+            Processor removeTimedBlock:unblock.
+            unblock := nil.
+        ].
     ].
 
     count := count - 1.
@@ -735,5 +781,5 @@
 !Semaphore class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic/Semaphore.st,v 1.62 2002-07-09 13:58:58 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Semaphore.st,v 1.63 2002-07-10 11:10:24 cg Exp $'
 ! !