Thanks Sven

I started to use the suggestions of Ben. 
And I’m thinking that I could show my version then step back and explain that 
indeed there is another process (the UI one). 
I will digest your example and produce a new version of the booklet and people 
can give feedback. 

S.

 

> On 10 Jan 2020, at 23:30, Sven Van Caekenberghe <[email protected]> wrote:
> 
> 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 <[email protected]> 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 <[email protected]> wrote:
>> 
>> 
>> On Thu, Jan 9, 2020 at 5:03 AM ducasse <[email protected]> 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