WorkerPool
struct WorkerPooln symmetric threads running the same body until asked to stop.
Move-only, like everything else in this library that owns threads: the
pool owns the header block and the ThreadGroup, and there must be
exactly one join and exactly one free.
Implements Movable
__init__
def __init__(out self, var group: ThreadGroup, header: Pointer[Int64, MutUntrackedOrigin], n: Int) Adopt an already-spawned group. Use WorkerPool.start instead.
- Args
group: The spawned threads.header: The heap header block backing them.n: The worker count.
__deinit__
def __deinit__(deinit self) Stop, join, then free the header — in that order, always.
A pool that is simply dropped still shuts down cleanly. Freeing the
header while a worker could still read it would be a use-after-free,
and Mojo's last-use destruction makes that easy to trigger by accident,
so the destructor pays for the join rather than trusting the caller to
have done it. If shutdown() was already called, the joins are no-ops
and this costs nothing.
A join failure here cannot be reported — a destructor cannot raise —
so it is swallowed. Call shutdown() explicitly if you want to see it.
start
def start[work: def(Int, Pointer[UInt8, MutUntrackedOrigin], AtomicFlag) thin -> None](n: Int, ctx: Pointer[UInt8, MutUntrackedOrigin]) -> Self Start n threads, each running work(index, ctx, stop_flag).
Every worker gets the same ctx and the same stop flag; the index is
the only thing that distinguishes them, and it is drawn from a counter
rather than baked in, so the workers are genuinely symmetric.
- Parameters
work: The worker body — thin and non-raising.
- Args
n: How many threads to start. Must be at least 1.ctx: The shared context handed to every worker. Must outlive the pool; since the destructor joins, outliving the pool value is enough.
Raises Error: If n < 1, or if any pthread_create fails. On a failed
spawn the threads that did start are joined before the error
propagates, and the header is freed.
def start[work: def(Int, Pointer[UInt8, MutUntrackedOrigin], AtomicFlag) thin -> None](ctx: Pointer[UInt8, MutUntrackedOrigin]) -> Self Start one thread per logical CPU.
- Parameters
work: The worker body.
- Args
ctx: The shared context.
Raises Error: If any pthread_create fails.
num_workers
def num_workers(self) -> Int How many threads this pool started.
stop_flag
def stop_flag(self) -> AtomicFlag The shared stop flag, as a view.
Useful when something other than the pool's owner needs to be able to
ask for a stop — store flag.address() in your own context and rebuild
it with AtomicFlag.at.
request_stop
def request_stop(self) Ask every worker to return: release-store 1 to the stop flag.
Returns immediately. Cooperative — see the module docstring: a worker blocked in a syscall does not observe this until it returns.
is_stopping
def is_stopping(self) -> Bool Whether a stop has been requested.
join
def join(mut self) Wait for every worker to return.
Does not set the stop flag — call request_stop() first, or use
shutdown(). Joining a pool whose workers loop on the flag without
having requested a stop hangs forever, which is a bug in the caller,
not something this method can rescue.
Idempotent: ThreadHandle.join zeroes its id, so a second call is a
no-op.
Raises Error: The first pthread_join failure, after every thread has
had a join attempted.
shutdown
def shutdown(mut self) request_stop() then join() — the ordinary way to end a pool.
Raises Error: If a join fails. The stop has already been requested by then, so the workers are on their way out regardless.
pin_all
def pin_all(self) Pin worker i to CPU i % num_cpus() (a no-op on macOS).
Raises Error: On Linux if the affinity call fails.