Hi Marcel,
Post by marcel.taeumelHi Eliot,
with "Smalltalk processPreemptionYields == false", is there a Smalltalk way
to implement ProcessorScheduler >> #yield?
| semaphore |
<primitive: 167>
semaphore := Semaphore new.
[semaphore signal] fork.
semaphore wait.
So that works, right? Remember what the setting does:
If Smalltalk processPreemptionYields == false then when preempted by a
higher-priority process, the current process stays at the head of its run
queue. But when a process waits on a semaphore it is removed from its run
queue. When a process resumes it always gets added to the back of its run
queue. So in the above nothing changes. Either the primitive puts the
calling process to the back of its run queue, or (if the primitive is not
implemented):
1. the fork creates a new process, adding it to the end of the active
process's run queue
2. the wait in "semaphore wait" removes the active process from its run
queue and adds it to the semaphore, so the active process is now not
runnable, waiting on the semaphore, which
3. allows the next process in the run queue (if any) to run, and eventually
4. allows the newly forked process to run, and
5. the signal in "semaphore signal" removes the process from the semaphore
and adds it to the back of the run queue, so
6. all processes at the same priority level as the process that called
yield have run
Note that all the primitive does is to circumvent having to create a
semaphore and create and schedule a process, and do a signal and a wait to
move a process to the back of its run queue. This is worth while because
most of the time a process's run queue is empty, it being the only runnable
process at that priority.
Test it:
| yielded |
yielded := false.
[yielded := true] fork.
yielded
| yielded |
yielded := false.
[yielded := true] fork.
Processor yield.
yielded
OK, so there's clearly a lot of confusion about process preemption
yielding. I hope that the following two examples show what's different
between the two regimes. In the first example we merely create two
processes at a lower priority than the active process, at a priority where
there are no other processes:
| run priority process1 process2 |
run := true.
"*find an empty priority level at a priority lower than the active process*"
priority := Processor activePriority - 1.
[(Processor waitingProcessesAt: priority) isEmpty] whileFalse:
[priority := priority - 1].
"*Create two processes at that priority*"
process1 := [[run] whileTrue] forkAt: priority.
process2 := [[run] whileTrue] forkAt: priority.
"*Check that their order in the list is the same as the order in which they
were created*"
self assert: (Processor waitingProcessesAt: priority) first == process1.
self assert: (Processor waitingProcessesAt: priority) last == process2.
"*set the flag to zero and block on a delay, allowing the processes to run
to termination*"
run := false.
(Delay forMilliseconds: 50) wait.
"*Check that they have indeed terminated*"
self assert: (Processor waitingProcessesAt: priority) isEmpty
So far so good. Now let's preempt process1 while it is running, by waiting
on a delay without setting run to false:
| run priority process1 process2 |
run := true.
"*find an empty priority level at a priority lower than the active process*"
priority := Processor activePriority - 1.
[(Processor waitingProcessesAt: priority) isEmpty] whileFalse:
[priority := priority - 1].
"*Create two processes at that priority*"
process1 := [[run] whileTrue] forkAt: priority.
process2 := [[run] whileTrue] forkAt: priority.
"*Check that their order in the list is the same as the order in which they
were created*"
self assert: (Processor waitingProcessesAt: priority) first == process1.
self assert: (Processor waitingProcessesAt: priority) last == process2.
"*Now block on a delay, allowing the first one to run, spinning in its
loop.*
* When the delay ends the current process will preempt process1, because *
*process1** is at a lower priority.*"
(Delay forMilliseconds: 50) wait.
Smalltalk processPreemptionYields
ifTrue: "*If process preemption yields, process1 will get sent to the back
of the run queue (which is wrong; no process explicitly yielded)*"
[self assert: (Processor waitingProcessesAt: priority) first == process2.
self assert: (Processor waitingProcessesAt: priority) last == process1]
ifFalse: "*If process preemption doesn't yield, the processes retain their
order (yay! we indeed have cooperative scheduling within a priority).*"
[self assert: (Processor waitingProcessesAt: priority) first == process1.
self assert: (Processor waitingProcessesAt: priority) last == process2].
"*set the flag to zero and block on a delay, allowing the processes to run
to termination*"
run := false.
(Delay forMilliseconds: 50) wait.
"*Check that they have indeed terminated*"
self assert: (Processor waitingProcessesAt: priority) isEmpty
Run the above after trying both "Smalltalk processPreemptionYields: false"
and "Smalltalk processPreemptionYields: true"
Does the above clarify things? Nothing changes within priorities. What
the setting controls is what happens when a process is preempted by a
higher-priority process. The old code did an implicit yield of the
preempted process, destroying the guarantee of cooperative scheduling
within a priority.
I've attached the two examples above in a workspace text (in which
"Processor waitingProcessesAt: priority" is cached in a variable).
And another question: How to "yield" a process (i.e. put back in line) that
Post by marcel.taeumelis not currently running but runnable?
From a higher priority process one can directly manipulate the lists of
lower priority processes. So, e.g. if in the above we've created process1
and process2 and they're on the list, we can do things like
(Processor waitingProcessesAt: priority) addLast: (Processor
waitingProcessesAt: priority) removeFirst
Again, this is easier if Smalltalk processPreemptionYields is false. You
know that the only thing that can change the order of processes in a list
is either cooperative scheduling within those processes or explicit
manipulation of the list by higher priority processes.
Do you have a good model of the runnable processes? The active process is
always not on its list because it is running. There may be runnable, but
not running, processes at its priority which will be on the list at the
active process's priority. All other runnable processes are on lists at
lower priorities. Each priority has one list. Resuming a process (either
by resume or by aSemaphore signal) adds a previously not runnable process
to the end of the list at its priority (unless it is the highest-priority
runnable process, in which case it will become the active process).
Suspending a process, or causing it to wait on a semaphore, removes the
process from its list (unless it is the active process, in which case it
does not need to be removed).
This is interesting and important. Having implemented the Blue Book VM and
worked on the innards of the scheduler for more years than I'd care to
remember I have a very clear picture of the scheduler; it's simple, and
well designed. But Marcel, you're not sure of a number of things, which
makes me think you don't have that model in your head, and given that
you're an extremely strong Smalltalker, that implies that many other
Smalltalk programmers probably don't have that model in their heads
either. What can we do to explain the scheduler more clearly to Smalltalk
programmers as opposed to VM implementors?
Best,
Post by marcel.taeumelMarcel
--
http://forum.world.st/The-Trunk-Kernel-mt-1009-mcz-tp4887889p4888435.html
Sent from the Squeak - Dev mailing list archive at Nabble.com.
_,,,^..^,,,_
best, Eliot