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 > >
