A small but practical C++17 thread pool implementation with:
- fixed worker threads
std::future-based task submission- safe shutdown and join on destruction
CMakebuild- lightweight unit tests via
ctest
- C++17 interface
Submit()accepts arbitrary callables and returnsstd::futureWaitIdle()waits until the queue is empty and all active tasks are finishedShutdown()prevents new tasks and joins all worker threads- falls back to one worker when
thread_count == 0
thread-pool/
├── CMakeLists.txt
├── README.md
├── include/
│ └── thread_pool/
│ └── thread_pool.h
├── src/
│ └── thread_pool.cc
├── examples/
│ └── main.cpp
└── tests/
└── thread_pool_test.cpp
Recommended environment: WSL Ubuntu + CMake + Ninja + g++
cd ~/projects/thread-pool
cmake -S . -B build -G Ninja
cmake --build build
ctest --test-dir build --output-on-failureRun the example:
./build/thread_pool_exampleThe code itself is portable, but in this workspace the Windows-side CMake build directory had permission issues. If you want to build on Windows, prefer:
- source directory on a normal writable path
- a separate build directory outside the source tree
Developer PowerShellorvcvars64.bat
Example:
cmake -S D:\projects\thread-pool -B C:\Users\pc\build\thread-pool -G Ninja -DCMAKE_CXX_COMPILER=cl
cmake --build C:\Users\pc\build\thread-pool
ctest --test-dir C:\Users\pc\build\thread-pool --output-on-failureHeader: include/thread_pool/thread_pool.h
thread_pool::ThreadPool pool(4);
auto future = pool.Submit([](int x, int y) {
return x + y;
}, 1, 2);
pool.WaitIdle();
int result = future.get();
pool.Shutdown();Creates a fixed-size worker pool. If thread_count is 0, it creates one worker thread.
Packages the callable and its arguments into a task, pushes it into the queue, and returns a std::future for the result.
If the pool has already been shut down, this throws std::runtime_error.
Blocks until:
- the task queue is empty
- no worker is currently executing a task
Marks the pool as stopping, wakes all worker threads, and joins them.
Shutdown() is idempotent. The destructor also calls it automatically.
The current test target checks:
- submitted tasks return expected values
WaitIdle()waits for queued work- exceptions inside tasks are propagated through
future.get() - submitting after shutdown throws
- zero worker count falls back to one thread
Run tests with:
ctest --test-dir build --output-on-failureSubmit() supports arbitrary callables and arguments and uses:
std::invoke_result_tstd::packaged_taskstd::futurestd::apply
That gives a clean and type-safe interface without forcing callers into std::function<...> signatures.
Workers block on a condition variable, pop one task at a time, execute it, then update the active worker count.
The shutdown rule is simple:
- if stopping and queue empty, worker exits
- otherwise remaining queued tasks are drained before exit
Many small thread pool examples omit this. WaitIdle() is useful in tests and batch-style workloads where you want a precise synchronization point before reading results or shutting down external state.
This is the most error-prone part of a thread pool.
You have to define:
- whether shutdown drops queued tasks or drains them
- whether new submission is allowed during shutdown
- when worker threads are allowed to exit
This project chooses:
- no new submissions after shutdown
- queued tasks are drained
- worker threads exit only when stopping is true and the queue is empty
WaitIdle() is only correct if both conditions are tracked:
- queued task count
- currently running worker count
Checking only whether the queue is empty is wrong, because a worker may already have popped a task and still be executing it.
If a task throws and the worker thread does not contain that exception, the program can terminate.
Using std::packaged_task solves this cleanly because exceptions are captured and rethrown at future.get().
Submit() is a template, so it must stay in the header unless you redesign the API. This is a common C++ library design constraint and easy to get wrong when splitting .h and .cc.
- bounded task queue
StopNow()that drops queued work- task priority support
- dynamic thread resizing
- timeout-aware waiting
- replacing the custom test harness with GoogleTest
This project has been verified successfully in WSL:
cmakeconfigure: passedcmake --build: passedctest: passed