Thread Pool
A thread pool is a pre-created set of worker threads that pull work from a shared queue. The application submits jobs. The pool hands each job to a free worker. From ECE459 L09 (also from Hemal Shah crash course).
Why?
Spawning a thread per task is expensive. If tasks are short, the create + destroy cost dominates the actual work. A pool amortizes that cost across many jobs.
What you get:
- Workers are created once and reused
- Pool size scales to the hardware, so the app doesn’t need to know the core count
- Natural backpressure when all workers are busy
How many threads to use?
Depends on the workload:
- Compute-bound: fewer than the number of virtual CPUs
- I/O-bound: more, since threads spend most of their time blocked
Amdahl’s Law bounds the useful count either way
Implementations
- Java:
java.util.concurrent.ThreadPoolExecutor - C#:
System.Threading.ThreadPool - GLib:
GThreadPool - Rust: the
threadpoolcrate
Example
Example from L09. A pool of 8 workers, a shared VecDeque with 4000 jobs plus a -1 sentinel, and 4 consumer jobs submitted via pool.execute:
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use threadpool::ThreadPool;
fn main() {
let pool = ThreadPool::new(8);
let queue = Arc::new(Mutex::new(VecDeque::new()));
println!("main thread has id {}", thread_id::get());
for j in 0..4000 {
queue.lock().unwrap().push_back(j);
}
queue.lock().unwrap().push_back(-1);
for _ in 0..4 {
let queue_in_thread = queue.clone();
pool.execute(move || {
loop {
let mut q = queue_in_thread.lock().unwrap();
if !q.is_empty() {
let val = q.pop_front().unwrap();
if val == -1 {
q.push_back(-1);
println!("Thread {} got the signal to exit.", thread_id::get());
return;
}
println!("Thread {} got: {}!", thread_id::get(), val);
}
}
});
}
pool.join();
}One
execute= one job, not one-per-worker
pool.execute(...)queues one job. With 4 consumer loops and a pool of 8, only 4 workers actually run. That’s why the loop submits the consumer closure 4 times, once per worker that should run it.
Output is interleaved since workers race for the queue lock:
main thread has id 4455538112
Thread 123145474433024 got: 0!
Thread 123145474433024 got: 1!
Thread 123145474433024 got: 2!
...
Thread 123145478651904 got: 3999!
Thread 123145476542464 got the signal to exit.
Thread 123145484980224 got the signal to exit.
Thread 123145474433024 got the signal to exit.
Thread 123145478651904 got the signal to exit.
The -1 sentinel gets pushed back after each consumer sees it, so every worker eventually exits cleanly.