Concurrency
Smalltalk processes are lightweight threads scheduled by the VM inside a single OS thread (Pharo and Squeak; VisualWorks and VA similarly).
34.1 Processes (green threads)
Smalltalk processes are lightweight threads scheduled by the VM inside a single OS thread (Pharo and Squeak; VisualWorks and VA similarly). Scheduling is preemptive by priority, cooperative within a priority.
Priorities (lowest to highest): systemBackgroundPriority, userBackgroundPriority, userSchedulingPriority (the UI process), userInterruptPriority, lowIOPriority, highIOPriority, timingPriority. Use newProcess to create without starting, then resume.
| p |
p := [ 1 to: 5 do: [ :i | Transcript crShow: 'worker ' , i printString. (Delay forMilliseconds: 200) wait ] ] fork.
Transcript crShow: 'main continues'.
p := [ 10 timesRepeat: [ Transcript crShow: 'bg'. 100 milliSeconds wait ] ]
forkAt: Processor userBackgroundPriority.
p name: 'my background job'.
p suspend. p resume. p terminate. p isTerminated.
Processor activeProcess. Processor yield. "current process; let others of same priority run"34.2 Delays
(Delay forSeconds: 2) wait, (Delay forMilliseconds: 500) wait, or 2 seconds wait (Pharo). Waiting in the UI process freezes the UI — do long work in a forked process.
34.3 Semaphores
| sem result |
sem := Semaphore new.
[ result := 100 factorial. sem signal ] fork.
sem wait. "blocks until signaled"
result printString size.
sem waitTimeoutMSecs: 1000. "→ true if timed out (Pharo/Squeak)"34.4 Mutual exclusion
| mutex counter |
mutex := Mutex new. "re-entrant (same process may re-enter)"
counter := 0.
1 to: 10 do: [ :i |
[ 1000 timesRepeat: [ mutex critical: [ counter := counter + 1 ] ] ] fork ].
Semaphore forMutualExclusion critical: [ "..." ]. "non re-entrant"
Monitor new critical: [ "..." ]. "with wait/signal conditions"34.5 SharedQueue: producer/consumer
next blocks until an item is available; Pharo also offers AtomicSharedQueue and WaitfreeQueue.
| queue producer consumer |
queue := SharedQueue new.
producer := [ 1 to: 10 do: [ :i | queue nextPut: i. 50 milliSeconds wait ]. queue nextPut: #done ] fork.
consumer := [
| item |
[ (item := queue next) = #done ] whileFalse: [ Transcript crShow: 'consumed ' , item printString ] ] fork.34.6 Futures and promises
TaskIt (loadable from github://pharo-contributions/taskit) is the standard library for tasks, futures, and worker pools in Pharo: future := [ self longComputation ] future. future onSuccessDo: [ :v | ... ]. Simple futures can also be hand-written with a semaphore.
34.7 Real parallelism
Green threads give concurrency, not multi-core parallelism. For CPU parallelism:
Run several images (worker processes) communicating via sockets, HTTP, or a message queue.
Use FFI to call native multi-threaded libraries (Pharo's threaded FFI runs calls in a worker thread).
GemStone/S runs many Gems (VMs) in parallel against a shared transactional object space.
34.8 Pitfalls
Exceptions in forked processes open a debugger (UI) or are logged; wrap work with on:do:.
Never update Morphic UI directly from a background process: use UIManager default defer: [ ... ] or World defer: [ ... ] (Spec2: self application defer: [...] patterns).
Avoid busy loops: always wait on a Delay, Semaphore, or queue.