Waitgroups in Rust
Our example demonstrates how to wait for multiple threads to finish using a WaitGroup. In Rust, we’ll use the std::sync::Arc and std::sync::Barrier to achieve similar functionality.
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;
// This is the function we'll run in every thread.
fn worker(id: i32) {
println!("Worker {} starting", id);
// Sleep to simulate an expensive task.
thread::sleep(Duration::from_secs(1));
println!("Worker {} done", id);
}
fn main() {
// This Barrier is used to wait for all the
// threads launched here to finish.
let barrier = Arc::new(Barrier::new(5));
// Launch several threads and clone the Barrier
// for each.
let mut handles = vec![];
for i in 1..=5 {
let b = Arc::clone(&barrier);
// Spawn a new thread and store its handle
let handle = thread::spawn(move || {
worker(i);
// Wait for all threads to reach this point
b.wait();
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
// Note that this approach has no straightforward way
// to propagate errors from workers. For more
// advanced use cases, consider using channels or
// other synchronization primitives.
}To run the program:
$ cargo run
Worker 5 starting
Worker 1 starting
Worker 4 starting
Worker 3 starting
Worker 2 starting
Worker 4 done
Worker 1 done
Worker 2 done
Worker 5 done
Worker 3 doneThe order of workers starting up and finishing is likely to be different for each invocation.
In this Rust version:
We use
Arc<Barrier>instead ofWaitGroup.Arcprovides thread-safe reference counting, allowing us to share theBarrieracross multiple threads.The
Barrieris created with a count of 5, matching the number of threads we’ll spawn.We spawn threads using
thread::spawnand store their handles in a vector.Each thread calls
worker()and then waits at the barrier usingb.wait().In the main thread, we wait for all spawned threads to complete by calling
join()on each handle.
This approach ensures all threads complete before the program exits, similar to the WaitGroup in the original example. However, Rust’s ownership and borrowing rules provide additional safety guarantees at compile time.