Barrier
std::sync::Barrier is a multi-thread rendezvous primitive initialized with a thread count $N$ (Barrier::new(n)). When a thread calls barrier.wait(), it blocks until all $N$ threads have reached .wait(), at which point all $N$ threads are released simultaneously and the barrier automatically resets so it can be reused for the next phase!
Reusable Multi-Phase Synchronization
Unlike std::sync::Once (which triggers only once in the lifetime of the program), a Barrier is cyclic (reusable): after the $N$-th thread arrives and releases Phase 1, the exact same Barrier instance is immediately ready to synchronize Phase 2 in a multi-step simulation or iterative algorithm.
Barrier::new(3) Across Two Consecutive Phases:
Worker 0 ----(Phase 1)----> |B| ----(Phase 2)----> |B| ---> Done
Worker 1 --(Phase 1)------> |A| ------(Phase 2)--> |A| ---> Done
Worker 2 -------(Phase 1)-> |R| ---(Phase 2)-----> |R| ---> Done
^ ^
All 3 wait here Barrier resets automatically
before starting P2! for the end of Phase 2!
Leader Election via BarrierWaitResult::is_leader()
Every call to barrier.wait() returns a BarrierWaitResult. For each barrier phase across the $N$ participating threads:
- Exactly
1thread receiveswait_result.is_leader() == true. - The remaining
N - 1threads receivewait_result.is_leader() == false.
This built-in leader election lets a single thread perform serial phase-transition work (such as swapping read/write buffers, logging phase metrics, or checking convergence) before the next barrier wait without needing a separate mutex or atomic flag:
use std::sync::{Barrier, Mutex};
use std::thread;
fn main() {
const WORKERS: usize = 3;
const ROUNDS: usize = 2;
let barrier = Barrier::new(WORKERS);
let log = Mutex::new(Vec::new());
thread::scope(|s| {
for worker_id in 0..WORKERS {
let barrier_ref = &barrier;
let log_ref = &log;
s.spawn(move || {
for round in 1..=ROUNDS {
log_ref
.lock()
.unwrap()
.push(format!("round {round}: worker {worker_id}"));
// 1st wait: ensure all workers finished appending for `round`:
let result = barrier_ref.wait();
if result.is_leader() {
log_ref
.lock()
.unwrap()
.push(format!("=== end of round {round} (leader: {worker_id}) ==="));
}
// 2nd wait: ensure leader finished logging the summary before round + 1 starts:
barrier_ref.wait();
}
});
}
});
for entry in log.into_inner().unwrap() {
println!("{}", entry);
}
}
round 1: worker 0
round 1: worker 1
round 1: worker 2
=== end of round 1 (leader: 2) ===
round 2: worker 2
round 2: worker 0
round 2: worker 1
=== end of round 2 (leader: 1) ===
Avoiding Barrier Deadlocks
If a Barrier is constructed with Barrier::new(N) and fewer than N threads call .wait() (for example, because one of the threads returned early or panicked before reaching the barrier), the remaining threads waiting at the barrier will block forever. Always ensure every participating thread executes the exact same number of barrier.wait() calls.