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.
| Need | Use |
|---|---|
| Inputs a worker reads or owns | Shared borrows, or move the values in |
| Results to collect | task.join(), or send them on a channel |
| One independent counter or flag | An atomic |
| Several values that must change together | A mutex |
| State that executor jobs share | A Shared handle to an atomic or a mutex |
| A request to stop | A 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.