Advanced Projects
Thread Pool
A thread pool maintains a fixed set of pre-spawned OS worker threads that wait on a shared synchronization queue for boxed closures (Box<dyn FnOnce() + Send + 'static>) and provides a Future-like result handle (JobHandle<T>) plus deterministic graceful shutdown in Drop.
Thread Pool Architecture
+-------------------------------------------------------------------+
| ThreadPool |
| sender: Option<mpsc::Sender<Job>> |
| workers: Vec<JoinHandle<()>> |
+------------------------------+------------------------------------+
|
mpsc::channel() | Arc<Mutex<mpsc::Receiver<Job>>>
v
+---------------------+---------------------+
| | |
v v v
+-----------+ +-----------+ +-----------+
| Worker 0 | | Worker 1 | | Worker 2 |
+-----------+ +-----------+ +-----------+
Graceful Shutdown in Drop:
1. Take `self.sender` and drop it -> closes channel
2. Workers receive `Err(RecvError)` once queued jobs finish
3. Join every worker `JoinHandle`
| Component | Type | Responsibility |
|---|---|---|
Job |
Box<dyn FnOnce() + Send + 'static> |
Type-erased unit of work transferable across threads |
| Shared receiver | Arc<Mutex<mpsc::Receiver<Job>>> |
Serializes recv() acquisition across worker threads |
TaskHandle<T> |
mpsc::Receiver<T> |
Allows the caller to block on a typed return value from a submitted closure |
Drop for ThreadPool |
Takes Option<Sender<Job>> |
Drops sender before joining workers so no thread deadlocks waiting for work |
Complete Thread Pool Implementation
use std::sync::{Arc, Mutex, mpsc};
use std::thread::{self, JoinHandle};
type Job = Box<dyn FnOnce() + Send + 'static>;
pub struct TaskHandle<T> {
rx: mpsc::Receiver<T>,
}
impl<T> TaskHandle<T> {
pub fn wait(self) -> Result<T, mpsc::RecvError> {
self.rx.recv()
}
}
pub struct ThreadPool {
workers: Vec<JoinHandle<()>>,
sender: Option<mpsc::Sender<Job>>,
}
impl ThreadPool {
pub fn new(num_workers: usize) -> Self {
assert!(num_workers > 0, "ThreadPool requires at least 1 worker");
let (sender, receiver) = mpsc::channel::<Job>();
let shared_rx = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(num_workers);
for id in 0..num_workers {
let rx = Arc::clone(&shared_rx);
let handle = thread::Builder::new()
.name(format!("pool-worker-{id}"))
.spawn(move || loop {
let message = {
let guard = rx.lock().expect("receiver mutex poisoned");
guard.recv()
};
match message {
Ok(job) => job(),
Err(_) => break,
}
})
.expect("failed to spawn worker thread");
workers.push(handle);
}
Self {
workers,
sender: Some(sender),
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
if let Some(tx) = &self.sender {
tx.send(Box::new(f)).expect("thread pool has shut down");
}
}
pub fn spawn_with_handle<F, T>(&self, f: F) -> TaskHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let (result_tx, result_rx) = mpsc::sync_channel(1);
self.execute(move || {
let output = f();
let _ = result_tx.send(output);
});
TaskHandle { rx: result_rx }
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
// Drop the sender first so workers observe channel disconnection
drop(self.sender.take());
for handle in self.workers.drain(..) {
let _ = handle.join();
}
}
}
fn is_prime(n: u64) -> bool {
if n < 2 {
return false;
}
if n % 2 == 0 {
return n == 2;
}
let mut d = 3;
while d * d <= n {
if n % d == 0 {
return false;
}
d += 2;
}
true
}
fn main() {
let pool = ThreadPool::new(4);
let ranges = [
(1u64, 25_000u64),
(25_001, 50_000),
(50_001, 75_000),
(75_001, 100_000),
];
let handles: Vec<TaskHandle<(u64, u64, usize)>> = ranges
.into_iter()
.map(|(lo, hi)| {
pool.spawn_with_handle(move || {
let count = (lo..=hi).filter(|&x| is_prime(x)).count();
(lo, hi, count)
})
})
.collect();
let mut total_primes = 0;
for handle in handles {
let (lo, hi, count) = handle.wait().unwrap();
println!("Range {:>6}..={:>6} -> {:>4} primes", lo, hi, count);
total_primes += count;
}
println!("Total primes in 1..=100,000: {}", total_primes);
}
Range 1..= 25000 -> 2762 primes
Range 25001..= 50000 -> 2371 primes
Range 50001..= 75000 -> 2260 primes
Range 75001..=100000 -> 2199 primes
Total primes in 1..=100,000: 9592