文档目录

一、为什么需要线程池?

不用线程池:来一个任务就 new thread(),做完就 join()。问题是线程创建/销毁开销很大(~10μs),且大量线程同时运行导致上下文切换爆炸。

线程池的方案:预先创建一组线程,它们空闲时等待任务,任务来了就执行。复用线程,避免创建销毁开销。

二、最小实现

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

public:
    ThreadPool(size_t threads) {
        for (size_t i = 0; i < threads; i++) {
            workers_.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(mtx_);
                        cv_.wait(lock, [this] {
                            return stop_ || !tasks_.empty();
                        });
                        if (stop_ && tasks_.empty()) return;
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    task();  // 在锁外执行任务!
                }
            });
        }
    }

    template <typename F>
    void enqueue(F&& f) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            tasks_.push(std::forward<F>(f));
        }
        cv_.notify_one();
    }

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

三、关键设计决策

1. 为什么 task() 要在锁外执行?

// ❌ 错误:在锁内执行任务
{
    std::lock_guard<std::mutex> lock(mtx_);
    task = tasks_.front();
    tasks_.pop();
    task();  // 如果 task 执行时间长,其他线程和 enqueue 都被阻塞
}

// ✅ 正确:锁内只取任务,锁外执行
{
    std::lock_guard<std::mutex> lock(mtx_);
    task = std::move(tasks_.front());
    tasks_.pop();
}
task();

2. 为什么 stop_ 和 tasks_.empty() 要一起判断?

cv_.wait(lock, pred) 等价于:while (!pred()) { wait(lock); }。停止时有三种情况:

  • stop_=true, 队列空 → 线程应该退出
  • stop_=true, 队列非空 → 先执行完剩余任务再退出
  • stop_=false, 队列空 → 等待新任务

所以条件必须是 stop_ || !tasks_.empty()。