liveBook · Manning

Thread pool

As explained in C++ Concurrency in Action, Second Edition

A thread pool is a managed collection of reusable threads that execute submitted tasks.

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

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Works through a minimal C++ implementation and its shutdown behavior, while identifying missing completion and return-value support.
Examples
Uses a simple C++ pool listing and parallel quicksort's stack of pending chunks.
What it assumes
Builds on a thread-safe queue and a thread-joining helper, but explains the pool's task-and-worker model from the beginning.
What you can do after
Build it from scratch as a basic queued-worker pool, while recognizing its completion and contention limitations.
How it connects
Connects thread pools to task queues, hardware concurrency, synchronization, parallel quicksort, and work-list contention.

Also explained in

Compare how 10 books explain thread pool →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.