A thread pool shares a limited group of worker threads among tasks that can run concurrently. Submitting a task places it in a queue of pending work. A worker takes a task, executes it, then returns to the queue for another. This avoids creating a separate thread for every potential task, which can exhaust resources or increase context-switching costs. section 9.1 section 1.2.3
A minimal C++ pool
A simple implementation stores a thread-safe queue of std::function<void()> tasks and a vector of std::thread workers. Its submit() function wraps a supplied callable and pushes it onto the queue. Each worker repeatedly tries to pop a task: if one is available, it calls the task; otherwise, it yields before trying again. The constructor starts a worker count based on std::thread::hardware_concurrency(). section 9.1.1
The simple design leaves important decisions and limits visible:
- It offers no task return value or built-in way to wait for completion; callers needing a result or completion signal must arrange synchronization themselves. section 9.1.1
- Thread startup can throw. A done flag and join_threads helper ensure already-started workers are stopped and joined; member declaration order keeps the queue alive until the workers have stopped. section 9.1.1
- Choosing a worker count and distributing work efficiently still matter. A shared work list can become a contention point even when its stack implementation is lock-free. section 9.1 section 8.1.2
Parallel quicksort illustrates the broader pattern: threads repeatedly take pending chunks, sort them, and seek more work. Limiting the thread count helps avoid excessive task switching, but managing the workers and their shared chunks adds complexity. A pool is therefore a way to manage concurrent work, not a guarantee of better performance. section 8.1.2 section 1.2.3
Listing 9.1. Simple thread pool
class thread_pool
{
std::atomic_bool done;
threadsafe_queue<std::function<void()> > work_queue; #1
std::vector<std::thread> threads; #2
join_threads joiner; #3
void worker_thread()
{
while(!done) #4
{
std::function<void()> task;
if(work_queue.try_pop(task)) #5
{
task(); #6
}
else
{
std::this_thread::yield(); #7
}
}
}
public:
thread_pool():
done(false),joiner(threads)
{
unsigned const thread_count=std::thread::hardware_concurrency(); #8
try
{
for(unsigned i=0;i<thread_count;++i)
{
threads.push_back(
std::thread(&thread_pool::worker_thread,this)); #9
}
}
catch(...)
{
done=true; #10
throw;
}
}
~thread_pool()
{
done=true; #11
}
template<typename FunctionType>
void submit(FunctionType f)
{
work_queue.push(std::function<void()>(f)); #12
}
};
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":3,\"ch\":75},{\"line\":3,\"ch\":77}],[{\"line\":36,\"ch\":75},{\"line\":36,\"ch\":77}],[{\"line\":42,\"ch\":75},{\"line\":42,\"ch\":77}],[{\"line\":47,\"ch\":75},{\"line\":47,\"ch\":77}],[{\"line\":4,\"ch\":75},{\"line\":4,\"ch\":77}],[{\"line\":5,\"ch\":75},{\"line\":5,\"ch\":77}],[{\"line\":8,\"ch\":75},{\"line\":8,\"ch\":77}],[{\"line\":11,\"ch\":75},{\"line\":11,\"ch\":77}],[{\"line\":13,\"ch\":75},{\"line\":13,\"ch\":77}],[{\"line\":17,\"ch\":75},{\"line\":17,\"ch\":77}],[{\"line\":25,\"ch\":75},{\"line\":25,\"ch\":77}],[{\"line\":31,\"ch\":75},{\"line\":31,\"ch\":77}],[{\"line\":36,\"ch\":75},{\"line\":36,\"ch\":78}],[{\"line\":42,\"ch\":75},{\"line\":42,\"ch\":78}],[{\"line\":47,\"ch\":75},{\"line\":47,\"ch\":78}]]"}
!@%STYLE%@!
Inspect the worker loop and member declarations together: the loop shows how tasks are reused across workers, while declaration order supports safe shutdown. section 9.1.1









