Thread pool in Rust

Rust's standard library has std::thread but no built-in thread pool. Common approaches: the rayon crate for CPU-bound parallelism, the threadpool crate for a classic job queue, or a manual pool with mpsc channels. See What is Thread Pool?.

1. rayon — global thread pool (data parallelism)

Most idiomatic for CPU-bound work. Rayon maintains a work-stealing pool and provides parallel iterators — no manual queue needed.


use rayon::prelude::*;

fn main() {
    let nums: Vec<i32> = (0..10).collect();

    // rayon splits work across its internal thread pool
    let sum: i32 = nums.par_iter().map(|x| x * 2).sum();
    println!("sum = {}", sum);

    nums.par_iter().for_each(|x| {
        println!("task {} on {:?}", x, std::thread::current().id());
    });
}
      

2. threadpool crate — classic job pool

Submit closures to a fixed-size pool; closest to Java ExecutorService.


use threadpool::ThreadPool;

fn main() {
    let pool = ThreadPool::new(4);

    for i in 0..10 {
        pool.execute(move || {
            println!("task {} on {:?}", i, std::thread::current().id());
        });
    }

    pool.join(); // wait for all tasks
}
      

3. Manual pool — std::thread + mpsc

Build your own with worker threads and a channel — no external crate beyond std.


use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel::<Box<dyn FnOnce() + Send>>();
    let rx = std::sync::Arc::new(std::sync::Mutex::new(rx));

    let num_workers = 4;
    for _ in 0..num_workers {
        let rx = std::sync::Arc::clone(&rx);
        thread::spawn(move || loop {
            let job = rx.lock().unwrap().recv();
            match job {
                Ok(f) => f(),
                Err(_) => break,
            }
        });
    }

    for i in 0..10 {
        tx.send(Box::new(move || println!("task {}", i))).unwrap();
    }
    drop(tx); // close channel when done submitting
    thread::sleep(std::time::Duration::from_secs(1));
}
      

4. Tokio — blocking thread pool (async runtime)

For async code, CPU-heavy or blocking work should run on Tokio's blocking pool via spawn_blocking — not on async tasks.


use tokio::task;

#[tokio::main]
async fn main() {
    let handle = task::spawn_blocking(|| {
        // runs on tokio's blocking thread pool
        std::thread::sleep(std::time::Duration::from_millis(100));
        42
    });

    let result = handle.await.unwrap();
    println!("result = {}", result);
}
      

Summary: Use rayon for parallel loops, threadpool for explicit submit/join, or Tokio's blocking pool inside async applications.