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