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