Hi Steph,

On Jan 10, 2020, at 12:42 PM, ducasse <steph...@netcourrier.com> wrote:

Yes this is why in my chapter on Exception I show the VM code while some
people told me that it was not interesting.
And this is why in the current chapter on semaphore I have a section on the
implementation.
Now it does not mean that the we cannot have a higher view too :).


Indeed.  Note that now we have two improvements supported by the VM over
the blue book scheduler & synchronization primitives.

First we have a native critical section which is not a queuing semaphore,
but a queueing lock, which is in one of three states. It is a replacement
for the old Mutex class.  It is not in Pharo yet but you can easily adapt
the Squeak implementation (see below). Let me give a similar definition.

A native critical section is a queue that can have an owner.  A new native
critical section is empty and unowned.

A process attempts to enter the native critical section via
primitiveEnterCriticalSection. This occurs in one of three ways.
- If the native critical section is unowned (which implies it is empty)
then the process becomes the owner of the native critical section, the
primitive answers false (meaning that it was previously unowned or owned by
some other process), and the process proceeds.
- if the native critical section is already owned by the process then the
process remains the owner, the primitive answers true (meaning that it is
already owned) and the process proceeds.
- if the native critical section is already owned by some other process
then the process is suspended and added to the end of the native critical
section‘s queue, where it will wait until a primitiveExitCriticalSection
resumes it and makes it owner.

A process leaves a native critical section via
primitiveExitCriticalSection.  It is the process's responsibility to use
primitiveExitCriticalSection only when it entered via a
primitiveEnterCriticalSection that answered false.  If the native critical
section is empty then on executing primitiveExitCriticalSection it becomes
unowned.  If the native critical section is not empty then on executing
primitiveExitCriticalSection it becomes owned by the first process in its
queue, the process is removed from the queue and is scheduled, proceeding
from the primitiveEnterCriticalSection which caused it to block with
primitiveEnterCriticalSection answering false.

In addition a process may test and set its ownership of a native critical
section without danger of blocking.  A process tests and sets its ownership
of a native critical section via
primitiveTestAndSetOwnershipOfCriticalSection.  On executing
primitiveTestAndSetOwnershipOfCriticalSection, if the native critical
section is unowned then the process becomes its owner and
primitiveTestAndSetOwnershipOfCriticalSection answers false.  If the native
critical section is owned  by the process
primitiveTestAndSetOwnershipOfCriticalSection answers true.  If the native
critical section is owned by some other process then
primitiveTestAndSetOwnershipOfCriticalSection answers nil.

Using primitiveExitCriticalSection and primitiveExitCriticalSection allows
for efficient implemntation of rentrant critical sections:

critical: aBlock
"Evaluate aBlock protected by the receiver."
<criticalSection>
^self primitiveEnterCriticalSection
ifTrue: [aBlock value]
ifFalse: [aBlock ensure: [self primitiveExitCriticalSection]]

Adding primitiveTestAndSetOwnershipOfCriticalSection makes it easy to
implement and understand the following:

critical: aBlock ifLocked: lockedBlock
"Answer the evaluation of aBlock protected by the receiver.  If it is
already in a critical
section on behalf of some other process answer the evaluation of
lockedBlock."
<criticalSection>
^self primitiveTestAndSetOwnershipOfCriticalSection
ifNil: [lockedBlock value]
ifNotNil:
[:alreadyOwner|
alreadyOwner
ifTrue: [aBlock value]
ifFalse: [aBlock ensure: [self primitiveExitCriticalSection]]]

Once a process owns a critical section it can enter the criutical section
as many times as it wants.  With the Semaphore it can only enter once per
signal.  Of course we have constructed the class Mutex to operate similarly
to a native critical section, but it is inefficient and not entirely safe
(because we rely on implementation-defined behaviour to be able to assign
the Mutex's owner without being preempted.

In Squeak we already replaced the old Mutex with a Mutex built using the
native crittical section reoresentation and primitives.  The file-in is
attached.  An implementation note is that semaphores and native crtitical
sections look very similar; they are both queues so their first and second
and  instance variables are firstLink & lastLink, inherited from
LinkedList.  A Semaphore's third inst var is excessSignals, its excess
signals count.  A Mutex's third inst var is is owner.

HTH

P.S.
This is an interesting exercise.  What we have done in specifying behavior
here is focus on processes.  The documentation on the primitive methods in
the system focus on the semaphore or native critical section.  What (I
think) programmers want is to understand how the process behaves, not
understand how the semaphore or native critical section works.  So
documenting things from a process perspective is more useful.

P.P.S.  If you compare the performance of the constructed Mutex against the
native Mitex please report the results.

P.P.P.S. We had tio step carefully to replace the old Mutex with the new
one.  I can't remember her the details, but we handled it with Monticello
load scripts and we can find the details if you need them


On 10 Jan 2020, at 18:29, Nicolas Cellier <
nicolas.cellier.aka.n...@gmail.com> wrote:


For example, whether a Semaphore would queue waiting process by order of
registration (thru a linked list for example) or by order of priority (thru
a Heap for example), would completely change its behavior.
So isn't that kind of implementation detail SUPER important, especially
when hidden in VM?

Also, describing HOW it works is very often used as a mean to explain (and
make understand) a higher level feature.
IMO, understanding a feature from one implementation is as useful as
understanding a feature by examples of usage.
Even when implementation is plain Smalltalk, it's already an added value to
give main guidelines to help reading code (see this particular class or
message for understanding the core...), so when it's in VM...

Le ven. 10 janv. 2020 à 12:59, Danil Osipchuk <danil.osipc...@gmail.com> a
écrit :

> I didn't claim expertise on the subject (although I use semaphores
> extensively), nor its simplicity, nor that the implementation description
> should be the only guide on its usage (hence 'to add..., how it works'
> wording)
> Said that, to me it is the case, when a clear description of what is going
> on aids a lot. Instead of trying to define some rules and scenarios
> abstractly - to help a user to reason about the system behavior (isn't Stef
> was willing to look into VM code for this reason?).
>
> To me both scenarios of Stef could be explained that in first case the
> 'signal' process is not getting preempted by the 'wait' process of the same
> priority, while in second the preemption happens upon return from primitive
> (hopefully my memory serves me well and my understanding is correct).
>
> A tangent note on comments in general -- I've noticed more than once, that
> people tend to produce far  clearer descriptions in exchanges like this --
> when discussing matter with others.
> When a person is in documentation/comment writing mode he/she is sort of
> tenses up in a formal state and often produces something not very helpful.
> Current class comment of Semaphore is a perfect example, if I were not
> familiar with the concept from other sources, I would not be able to make
> any sense of it. So, I would suggest to use opportunities like this to
> improve comments/docs when a bit of knowledge shows up in a discussion.
>
>
>
> regards,
>  Danil
>
> пт, 10 янв. 2020 г. в 13:09, Sven Van Caekenberghe <s...@stfx.eu>:
>
>> Actually, it is just a, albeit concise, description of how Semaphores are
>> implemented.
>>
>> It does not help much in understanding them, in learning how they
>> can/should be used, for what purposes and how code behaves.
>>
>> Understanding of Process, priorities and Scheduling are also needed for a
>> more complete understanding.
>>
>> This is not a simple subject.
>>
>> Read https://en.wikipedia.org/wiki/Semaphore_(programming) and see how
>> well you understand the subject.
>>
>> In short, it does not answer Stef's concrete question(s).
>>
>> > On 10 Jan 2020, at 06:30, Danil Osipchuk <danil.osipc...@gmail.com>
>> wrote:
>> >
>> > Maybe to add this into the class comment, this is the most concise and
>> clear description of how it works i've ever seen
>> >
>> > пт, 10 янв. 2020 г., 8:13 Eliot Miranda <eliot.mira...@gmail.com>:
>> >
>> >
>> > 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.
>> >
>> > 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.
>
>

Attachment: Mutex.st
Description: Binary data

Attachment: Monitor.st
Description: Binary data

Reply via email to