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.