#288 Jul 29, 2026

288. std::sync::Barrier — Make All Threads Start Phase 2 Together

Your workers finish phase 1 at different speeds, and the fast ones charge into phase 2 while the slow ones are still writing. The hand-rolled fix is an atomic counter and a spin loop — std already ships the real thing.

The hack everyone writes first: bump an AtomicUsize when done, then spin until it hits n. It burns CPU while waiting and resetting it for the next round is a race of its own.

std::sync::Barrier (stable since 1.0) is that counter done right. Barrier::new(n) sets the threshold; every thread that calls .wait() blocks until the n-th one arrives, then all of them are released at once:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::sync::Barrier;
use std::thread;

let n = 4;
let barrier = Barrier::new(n);

thread::scope(|s| {
    for id in 0..n {
        let barrier = &barrier;
        s.spawn(move || {
            prepare(id);    // phase 1, each at its own pace
            barrier.wait(); // block until all 4 arrive
            process(id);    // phase 2 starts in lockstep
        });
    }
});

With scoped threads a shadowed &barrier is all the sharing you need — no Arc. (The move is for id; the reference moves with it.) Under plain thread::spawn, wrap the barrier in Arc as usual.

Two details the DIY counter doesn’t give you. First, .wait() returns a BarrierWaitResult, and exactly one thread per round gets is_leader() == true — a built-in election for “someone swap the buffers before the next phase”:

1
2
3
if barrier.wait().is_leader() {
    // exactly one thread runs this per round
}

Second, the barrier resets itself after releasing n threads. Call .wait() in a loop and you get phase-synchronized rounds for free — the classic shape of iterative simulations: compute a step, wait, leader swaps front/back buffers, wait, repeat.

One trap: the count is fixed at construction. If a thread panics before reaching .wait(), the rest block forever — there’s no timeout variant. Keep the work between barriers panic-free, or use channels when workers can drop out.

← Previous 287. thread::Builder — Name Your Workers So Panics Tell You Who Died Next → 289. JoinHandle::is_finished — Check on a Worker Without Blocking on join()