Hi Ben,

Great approach, though I would make one change to make your example completely 
copy/paste runnable.

Stef's original example:

| trace semaphore p1 p2 |

semaphore := Semaphore new.

trace := [ :message | 
        ('[{1}] {2}' format: { Processor activeProcess priority. message }) 
crLog ].

p1 := [ 
        semaphore wait.
        trace value: 'Process 1' ] fork.

p2 := [
        semaphore signal.
        trace value: 'Process 2' ] fork.  

trace value: 'Original process pre-yield'.
Processor yield.
trace value: 'Original process post-yield'.  

Gives:

'[40] Original process pre-yield'
'[40] Process 2'
'[40] Original process post-yield'
'[40] Process 1'

But not running the yield section gives:

'[40] Process 2'
'[40] Process 1'

From this it would seem that the code in p2 continues after signal and only 
later does p1 get past its wait.

Playing with the priorities we can change that order (apparently);

| trace semaphore p1 p2 |

semaphore := Semaphore new.

trace := [ :message | 
        ('[{1}] {2}' format: { Processor activeProcess priority. message }) 
crLog ].

p1 := [ 
        semaphore wait.
        trace value: 'Process 1' ] forkAt: 30.

p2 := [
        semaphore signal.
        trace value: 'Process 2' ] forkAt: 20.  

Gives:

'[30] Process 1'
'[20] Process 2'

Again, the yield section makes no difference. So something else happened.

The other way around:

| trace semaphore p1 p2 |

semaphore := Semaphore new.

trace := [ :message | 
        ('[{1}] {2}' format: { Processor activeProcess priority. message }) 
crLog ].

p1 := [ 
        semaphore wait.
        trace value: 'Process 1' ] forkAt: 50.

p2 := [
        semaphore signal.
        trace value: 'Process 2' ] forkAt: 60.  

Gives:

'[60] Process 2'
'[50] Process 1'

Obviously the details about scheduling, order and priorities are really 
important to understanding this behaviour, and we should be able to explain 
this is simple terms, so that normal people can use this correctly.

Sven

> On 10 Jan 2020, at 19:02, Ben Coman <b...@openinworld.com> wrote:
> 
> For greater visibility and comprehension, it might be useful to early in the 
> manual define a utility method...
> Object>>crTracePriority
>       self crTrace: '[', Processor activePriority printString, ']', self 
> printString  
> 
> On Fri, 10 Jan 2020 at 13:13, Eliot Miranda <eliot.mira...@gmail.com> wrote:
> 
> 
> On Thu, Jan 9, 2020 at 5:03 AM ducasse <steph...@netcourrier.com> wrote:
> Hi
> 
> I wanted to explain
> 
> | semaphore p1 p2 |
> semaphore := Semaphore new.
> p1 := [ semaphore wait.
>         'p1' crTrace ] fork.
> 
> p2 := [semaphore signal.
>          'p2' crTrace ] fork.
> 
> displays p2 and p1. 
> but I would like explain clearly but it depends on the semantics of signal. 
> 
> 
> - ==p1== is scheduled and its execution starts to wait on the semaphore, so 
> it is removed from the run queue of the scheduler and added to the waiting 
> list of the semaphore.
> - ==p2== is scheduled and it signals the semaphore. The semaphore takes the 
> first waiting process (==p1==) and reschedule it by adding it to the end of 
> the suspended lists.
> 
> Since Smalltalk does not have a preemptive scheduler, neither p1 nor p2 will 
> start to run until something else happens after the execution of p1 := [...] 
> fork. p2 := [...] fork. So for example, if there is Processor yield then p1 
> can start to run.
> 
> So you need to add code to your example to be able to determine what will 
> happen.  The easiest thing would be to delay long enough that both can run.  
> 1 millisecond is more than enough.
> 
> This is a good point.  It may be useful for the example to be expanded to...
> 
>     | semaphore p1 p2 |
>     semaphore := Semaphore new.
>     p1 := [ semaphore wait.
>         'Process 1' crTracePriority ] fork.
> 
>     p2 := [semaphore signal.
>          'Process 2' crTracePriority ] fork.  
>  
>     'Original process pre-yield' crTracePriority .
>     1 milliSeconds wait.
>     'Original process post-yield' crTracePriority .  
>  
> which would produce==>
> 
> [40]'Original process pre-yield'
> [40]'Process 2'
> [40]'Process 1'
> [40]'Original process post-yield'
> 
> with other examples producing...
> 
> [40]'Original process pre-yield'
> [30]'Process 1'
> [20]'Process 2'
> [40]'Original process post-yield'
> 
> [60]'Process 2'
> [50]'Process 1'
> [40]'Original process pre-yield'
> [40]'Original process post-yield'
> 
>  
>  
> Now this sentence "The semaphore takes the first waiting process (==p1==) and 
> reschedule it by adding it to the end of the suspended lists.” is super 
> naive. Is the semaphore signalling scheduled? or not?
> 
> I would say these three things, something like this:
> 
> "A semaphore is a queue (implemented as a linked list) and an excess signals 
> count, which is a non-negative integer.  On instance creation a new semaphore 
> is empty and has a zero excess signals count.  A semaphore created for mutual 
> exclusion is empty and has an excess signals count of one."
> 
> "When a process waits on a semaphore, if the semaphore's excess signals count 
> is non-zero, then the excess signal count is decremented, and the process 
> proceeds.  But if the semaphore has a zero excess signals count then the 
> process is unscheduled and added to the end of the semaphore, after any other 
> processes that are queued on the semaphore."
> 
> "When a semaphore is signaled, if it is not empty, the first process is 
> removed from it and added to the runnable processes in the scheduler. If the 
> semaphore is empty its excess signals count is incremented.
> 
> Given these three statements it is easy to see how they work, how to use them 
> for mutual exclusion, etc.
> 
> 
> 
> signal
>         "Primitive. Send a signal through the receiver. If one or more 
> processes 
>         have been suspended trying to receive a signal, allow the first one 
> to 
>         proceed. If no process is waiting, remember the excess signal. 
> Essential. 
>         See Object documentation whatIsAPrimitive."
> 
>         <primitive: 85>
>         self primitiveFailed
> 
>         "self isEmpty    
>                 ifTrue: [excessSignals := excessSignals+1]    
>                 ifFalse: [Processor resume: self removeFirstLink]"
> 
> 
> I wanted to know what is really happening when a semaphore is signalled. 
> Now resume: does not exist on Processor. 
> 
> I will look in the VM code. 
> 
> 
> For quick reference, here is some relevant VM code (the StackInterpreter code 
> is a little simpler...)
> 
> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/pharo/headless/smalltalksrc/VMMaker/StackInterpreter.class.st#L1422-L1432
>   
> StackInterpreter class >> initializePrimitiveTable [
> ...
>     "Control Primitives (80-89)"
>     (85 primitiveSignal)
>     (86 primitiveWait)
> ...
> ]
> 
> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/pharo/headless/smalltalksrc/VMMaker/InterpreterPrimitives.class.st#L5385-L5396
>   
> InterpreterPrimitives >> primitiveWait [
>     | sema excessSignals activeProc |
>     sema := self stackTop.  "rcvr"
>     excessSignals := self fetchInteger: ExcessSignalsIndex ofObject: sema.
>     excessSignals > 0
>         ifTrue:
>             [self storeInteger: ExcessSignalsIndex ofObject: sema withValue: 
> excessSignals - 1]
>         ifFalse:
>             [activeProc := self activeProcess.
>              self addLastLink: activeProc toList: sema.
>              self transferTo: self wakeHighestPriority]
> ]
> 
> 
> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/pharo/headless/smalltalksrc/VMMaker/InterpreterPrimitives.class.st#L4045-L4049
>   
> InterpreterPrimitives >> primitiveSignal [
>     "Synchronously signal the semaphore.
>      This may change the active process as a result."
>     self synchronousSignal: self stackTop
> ]
> 
> 
> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/pharo/headless/smalltalksrc/VMMaker/StackInterpreter.class.st#L21662-L21679
>   
> StackInterpreter >> synchronousSignal: aSemaphore [ 
>     "Signal the given semaphore from within the interpreter.
>      Answer if the current process was preempted."
>     | excessSignals |
>     <inline: false>
>     (self isEmptyList: aSemaphore) ifTrue:
>         ["no process is waiting on this semaphore"
>          excessSignals := self fetchInteger: ExcessSignalsIndex ofObject: 
> aSemaphore.
>          self storeInteger: ExcessSignalsIndex
>             ofObject: aSemaphore
>             withValue: excessSignals + 1.
>          ^false].
> 
>     objectMemory ensureSemaphoreUnforwardedThroughContext: aSemaphore.
> 
>     ^self resume: (self removeFirstLinkOfList: aSemaphore)
>         preemptedYieldingIf: preemptionYields
> ]
> 
> 
> cheers -ben


Reply via email to