Rust Rayon Thread Pools
A rayon thread pool is a fixed set of threads that runs parallel work. Rayon sizes the global pool to the machine by default, but a program can build its own pool with its own thread count instead.
Size the global pool once
build_global sets the pool that every rayon call will use. It must run before any parallel work. It can only run once.
rayon::ThreadPoolBuilder::new().num_threads(2).build_global().unwrap();
println!("threads = {}", rayon::current_num_threads());Or build a pool of your own
build makes a private pool. The global pool stays untouched.
let pool = rayon::ThreadPoolBuilder::new().num_threads(3).build().unwrap();Run work inside your pool
install runs a closure inside the pool. The closure returns a value, just like a call.
let total = pool.install(|| (0..4).into_par_iter().sum::<i32>());
println!("sum = {}", total);join runs two jobs as one
join takes two closures. It may run them side by side. It hands back both results as a pair.
let pair = rayon::join(|| (1..=50).sum::<i32>(), || (1..=5).product::<i32>());
println!("join = {:?}", pair);scope spawns jobs that borrow
The two halves of counts are disjoint. Each spawned job takes one half. scope waits for both.
let mut counts = vec![0; 2];
rayon::scope(|s| {
let (a, b) = counts.split_at_mut(1);
s.spawn(|_| a[0] = 10);
s.spawn(|_| b[0] = 20);
});
println!("counts = {:?}", counts);Example
// Cargo.toml: rayon = "1"
use rayon::prelude::*;
fn main() {
rayon::ThreadPoolBuilder::new()
.num_threads(2)
.build_global()
.unwrap();
println!("global threads = {}", rayon::current_num_threads());
let pool = rayon::ThreadPoolBuilder::new().num_threads(3).build().unwrap();
let (inside, total) = pool.install(|| {
let inside = rayon::current_num_threads();
let total: i32 = (1..=100).into_par_iter().sum();
(inside, total)
});
println!("inside pool = {}", inside);
println!("pool total = {}", total);
let mut counts = vec![0; 2];
rayon::scope(|s| {
let (a, b) = counts.split_at_mut(1);
s.spawn(|_| a[0] = 10);
s.spawn(|_| b[0] = 20);
});
println!("counts = {:?}", counts);
}Key points
- The global pool starts on first use.
- After that, its size cannot change.
- Use current_num_threads() to ask the pool you are in how big it is
- Use a private pool when one part of a program needs its own limit.
- The rest of the program keeps the global pool.
- scope returns only after every job it spawned has finished.
- That wait is what makes borrowing safe.
- rayon::spawn is the loose tool.
- Its jobs must own everything they touch.
- They need 'static, just like thread::spawn.
- A rayon pool runs parallel jobs on a fixed set of threads.
- More threads than cores rarely helps.
- The workers end up waiting for a core.
In this example, main first sizes the global pool to two threads. The print proves it. Only two workers exist now. Next the code builds a private pool of three. install runs a sum inside that pool. The sum sees three threads, not two. The pool adds 1 to 100 and returns 5050. Last, scope splits a two-slot Vec into halves. One job writes 10. One job writes 20. scope returns after both jobs finish. The final Vec holds both writes.
Note
Rayon defaults to one thread per core.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why must build_global run before any other rayon call?