🎓 A1Learner
🔍

Rust Barriers

A Barrier is a meeting point where threads wait until the whole group has arrived, then all set off together. Everyone loads, then everyone computes, then everyone merges, with nobody starting a phase early.

Make a barrier for the whole group

let gate = Barrier::new(4); // opens at the moment the 4th thread arrives

wait() sleeps until everyone arrives

gate.wait(); // this thread stops here until all 4 have called wait()

Exactly one thread is told it led

wait() hands back a result. is_leader() is true for one thread per meeting. Use it when a single thread should do a shared step:

use std::sync::{Arc, Barrier};
use std::thread;
fn main() {
    let gate = Arc::new(Barrier::new(3));
    let count = thread::scope(|s| {
        let mut handles = Vec::new();
        for _ in 0..3 {
            let g = Arc::clone(&gate);
            handles.push(s.spawn(move || g.wait().is_leader()));
        }
        handles.into_iter().map(|h| h.join().unwrap()).filter(|&b| b).count()
    });
    println!("leaders = {}", count);
}

A barrier resets itself for the next phase

After it opens, the same barrier gathers the group again — one barrier can pace a whole loop of phases:

for phase in 1..=3 {
    // ... every thread does its work for this phase ...
    gate.wait(); // regroup here before anyone starts the next phase
}

Example

use std::sync::{Arc, Barrier, Mutex};
use std::thread;

fn main() {
    let gate = Arc::new(Barrier::new(3));
    let log = Arc::new(Mutex::new(Vec::new()));
    thread::scope(|s| {
        for id in 0..3 {
            let gate = Arc::clone(&gate);
            let log = Arc::clone(&log);
            s.spawn(move || {
                log.lock().unwrap().push(format!("worker {} loaded", id));
                gate.wait();
                log.lock().unwrap().push(format!("worker {} computed", id));
                gate.wait();
            });
        }
    });
    let entries = log.lock().unwrap();
    let loaded = entries.iter().filter(|e| e.contains("loaded")).count();
    let computed = entries.iter().filter(|e| e.contains("computed")).count();
    let first_computed = entries.iter().position(|e| e.contains("computed")).unwrap();
    let last_loaded = entries.iter().rposition(|e| e.contains("loaded")).unwrap();
    println!("loaded lines = {}", loaded);
    println!("computed lines = {}", computed);
    println!("every load came before every compute = {}", last_loaded < first_computed);
}

Key points

  • Use Barrier::new(n) to make a meeting point for n threads.
  • wait() blocks until the nth thread arrives.
  • Then all of them continue together.
  • Every thread must arrive.
  • If one thread panics or leaves early, the others wait forever.
  • They wait at the barrier.
  • The count in new(n) must match the threads.
  • It must match the threads that will really come.
  • Use a barrier when work falls into phases.
  • The phases are load, compute, merge, repeat.
  • Use a channel when work flows one way.
  • It flows from producers to a consumer.
  • A barrier is cheap to share: wrap it in an Arc for spawned threads, or borrow it from scoped threads
  • Everything a thread wrote before the barrier is visible later.
  • Every thread after it can see those writes.
  • It is a Release and an Acquire in one place.

In this example, three workers each push a "loaded" line into a shared log, then meet at the barrier. None of them can pass until all three have arrived. So no "computed" line can be pushed yet. Every "loaded" line must exist first. Past the barrier they push their "computed" lines. Then they meet once more. The barrier has reset itself. It is ready for the next gathering. The scope joins the workers. main inspects the log. It holds 3 loaded lines and 3 computed lines. The position check proves the barrier's promise. The last load sits before the first compute. Lines may shuffle within a phase. The phases themselves cannot mix.

Note

A barrier orders memory as well as threads.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

One of four threads panics before reaching a Barrier::new(4). What happens to the other three?