A production-quality fixed-size worker pool with a bounded blocking task queue, std::future-based task submission, and graceful two-phase shutdown — built to demonstrate low-level systems programming, OS synchronization primitives, and memory safety.
Caller Thread(s)
pool.submit(f, args...) -> std::future<T>
│
▼
┌──────────────────────────────────────────────┐
│ Bounded Blocking Task Queue │
│ std::queue + std::mutex + CVs (Full/Empty) │
│ (Blocks if capacity reached: Back-pressure)│
└──────┬───────────────┬──────────────┬────────┘
│ │ │
▼ ▼ ▼
Worker 0 Worker 1 ... Worker N-1
(std::thread) (std::thread) (std::thread)
│ │ │
└───────────────┴──────────────┘
Execute tasks outside lock
- Bounded Queue & Back-Pressure: Hard capacity limit prevents unbounded heap consumption when producer throughput exceeds consumer throughput.
- Dual Condition Variables: Uses separate
not_empty_andnot_full_condition variables to eliminate spurious wakeups and busy spinning. - Lock Granularity: Tasks are popped under
std::unique_lock, but task execution occurs strictly outside the lock to guarantee true multi-core parallel execution. - Verified Race-Free: Tested and verified clean under ThreadSanitizer (TSan) with zero data races and zero deadlocks.
- Graceful Two-Phase Teardown: Signals shutdown flag under lock, notifies all sleeping workers via
notify_all(), drains remaining in-flight tasks, and joins all threads via RAII in the destructor.
Platform: Linux 5.15 (x86_64), GCC 13.3, 12 Hardware Threads (-O3 -march=native)
| Execution Mode | Wall Time | Speedup | Notes |
|---|---|---|---|
| Single-Threaded | 6,758.5 ms | 1.00x | Baseline serial execution |
std::async |
735.5 ms | 9.19x | Spawns OS threads dynamically |
| ThreadPool (12 Threads) | 744.7 ms | 9.08x | Near-linear scaling across 12 cores |
| Execution Mode | Execution Time | Overhead Analysis |
|---|---|---|
std::async (OS Thread Spawning) |
3,989.0 ms | Heavy syscall and thread allocation overhead |
| ThreadPool (Thread Reuse) | 1,255.9 ms | 3.2x faster — zero per-task kernel thread allocation |
Includes a comprehensive test suite covering concurrency edge cases:
SubmitAndGetFuture: Validates future extraction and packaged task execution.AllTasksComplete: 1,000 parallel tasks updating atomic counters withstd::memory_order_relaxed.ExceptionPropagates: Verifies exceptions thrown inside worker threads propagate cleanly to caller uponfuture.get().SubmitAfterShutdownThrows: Confirms post-shutdown task rejection.ConcurrentWritesDataRaceFree: 8 threads × 500 tasks stress-tested under ThreadSanitizer.BoundedQueueBackPressure: Asserts queue capacity bounding under worker starvation.ShutdownDrainsInFlight: Guarantees zero task loss during teardown.
# Build and run standalone unit tests
make test
# Build and run with ThreadSanitizer (TSan data-race validation)
make tsan
# Build and run with AddressSanitizer (ASan memory error check)
make asan
# Run performance benchmarks
make bench