spawn vs scope

`std::thread::spawn` returns a `JoinHandle<T>` and runs the closure on a fresh OS thread. The closure must be `'static` because the spawned thread can outlive the caller. `std::thread::scope` (stable since 1.63) introduces a scope that joins all child threads at the end, which lets you safely borrow stack data — no need for Arc when the data lives only for the scope.

Builder for stack size and naming

`thread::Builder` lets you set a thread name (visible to debuggers and in panic messages) and a custom stack size (default is 2 MiB). A name is invaluable when debugging crashes in a worker pool — every panic prints the thread name.

For deeper background, see a complete cheat-sheet of std collection complexities for the broader context behind this section.

Park, unpark, and condvars

`thread::park` blocks the current thread until something calls `unpark` on its handle. It's lower-level than channels but ideal for building custom synchronisation primitives. For traditional condition-variable patterns, pair `Mutex` with `std::sync::Condvar`.

Thread-local storage

`thread_local!` declares a value with one independent instance per thread. Useful for per-thread caches, RNG state, and so on. The macro returns a `LocalKey<T>` whose `with` method gives temporary access to the value.

For deeper background, see the official Rust API guidelines for module-level design for the broader context behind this section.

When to use threads vs async

Native threads are the right answer for CPU-bound work: image processing, compression, data-parallel computation. For tens of thousands of mostly-idle I/O tasks, an async runtime like Tokio scales better. Spawn a thread per CPU core with rayon or std::thread and an async runtime per machine — they compose, and you can hand work between them with channels.