Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Choose how workers share state

Prefer giving each worker what it needs and taking results back. Reach for shared mutable state only when workers must see each other’s changes while running.

NeedUse
Inputs a worker reads or ownsShared borrows, or move the values in
Results to collecttask.join(), or send them on a channel
One independent counter or flagAn atomic
Several values that must change togetherA mutex
State that executor jobs shareA Shared handle to an atomic or a mutex
A request to stopA cancellation token

Here each worker sums its own slice of the work and returns the result, so nothing is shared and nothing needs a lock:

def total(start: i64, end: i64) -> Result[i64, OverflowError[i64]]:
    mut sum = 0
    for value in range(start, end):
        sum = sum + value
    Ok(sum)

def main() -> Result[(), SpawnError[()] | IoError | OverflowError[i64]]:
    with spawn(total, 0, 500).drop_job()? as low, spawn(total, 500, 1000).drop_job()? as high:
        print(low.join()? + high.join()?)?
    Ok(())
499500

An atomic is the cheapest shared update, but each operation is separate: two atomics updated one after the other can be observed half-done. A mutex makes a whole block of updates appear at once, at the cost of making other threads wait.

Atomics and mutexes are shared by borrowing, so they work with scoped threads. Executor jobs must own their inputs, so they cannot borrow either one. Give each job its own owner of a Shared[Atomic[T]] or Shared[Mutex[T]] instead, or send jobs their data and collect their results.