Thanks ben, this is a nice idea to illustrate what is happening I will use it. Thanks for the VM code :). I like it too.
I will add a note also mentioning that indeed there is a process to execute the snippets that the reader will write and execute :). Stef PS: what makes me laugh a bit is that people talk about documentation when there are nearly none on the topics on which I write. I write to be able to fully forget everything and free my brain. I remember writing on bloc, exceptions, when there was only obscure texts or none as if mail discussions would make a book. At least this is not the level I want. To me I would like to have a book like Smalltalk and its implementation but about Pharo. This was a book! So I will write it piece by piece. > 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] > <mailto:[email protected]>> wrote: > > > On Thu, Jan 9, 2020 at 5:03 AM ducasse <[email protected] > <mailto:[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 > > <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 > > <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 > > <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 > > <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
