C++ Thread Pool Pattern: Real-World Engineering Guide

Problem Solved

Avoid the cost of creating/destroying threads for short tasks. Reuse threads to handle multiple tasks efficiently.

How It Works

  • A fixed or dynamic pool of worker threads waits for tasks
  • Tasks are dispatched to the next available worker
  • Workers process tasks and return to pool for next task

STL Usage

#include <thread>
#include <queue>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
using namespace std;

class ThreadPool {
private:
    vector<thread> workers_;
    queue<function<void()>> tasks_;
    mutex mtx_;
    condition_variable cv_;
    atomic<bool> stop_{false};

public:
    explicit ThreadPool(size_t num_threads) {
        for (size_t i = 0; i < num_threads; ++i) {
            workers_.emplace_back([this]() {
                while (true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(mtx_);
                        cv_.wait(lock, [this]() {
                            return stop_ || !tasks_.empty();
                        });
                        
                        if (stop_ && tasks_.empty()) return;
                        
                        task = tasks_.front();
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }

    template<class F>
    void enqueue(F&& f) {
        {
            lock_guard<mutex> lock(mtx_);
            tasks_.emplace(forward<F>(f));
        }
        cv_.notify_one();
    }

    ~ThreadPool() {
        {
            lock_guard<mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all();
        for (auto& worker : workers_) {
            worker.join();
        }
    }
};

Example

#include <iostream>
#include <chrono>
using namespace std;

void threadPoolExample() {
    ThreadPool pool(4);
    
    for (int i = 0; i < 100; ++i) {
        pool.enqueue([i]() {
            cout << "Task " << i << " executed" << endl;
        });
    }
    
    this_thread::sleep_for(chrono::seconds(2));
}

Use Cases

  • HTTP servers: Handle requests with worker threads
  • Async frameworks: Execute async operations
  • Task executors: Process background tasks
  • Parallel algorithms: Distribute computation

Key Takeaways

  • Reuses threads, avoiding creation overhead
  • Predictable resource usage
  • Scales well with task count
  • Common in production systems

Things to Be Careful About

  • Thread count: Too many threads cause context switching overhead
  • Task exceptions: Handle exceptions to prevent worker death
  • Shutdown: Ensure all tasks complete before destruction
  • Queue overflow: Bound queue size to prevent memory issues

Summary

Thread pools are essential for efficient task execution, providing predictable performance and resource usage.