线程池

内容介绍

线程池用固定数量线程处理大量任务,避免频繁创建和销毁线程。它是 C++ 写出高吞吐并发程序的关键组件之一。

C++ std::thread 是系统线程,不能把“任务数”直接映射成“线程数”。线程池的目标是让有限工作线程持续消费任务,从而减少创建成本、上下文切换和资源浪费。

简化线程池

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
 
class ThreadPool {
public:
    explicit ThreadPool(std::size_t n) {
        for (std::size_t i = 0; i < n; ++i) {
            workers_.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(m_);
                        cv_.wait(lock, [this] {
                            return stopping_ || !tasks_.empty();
                        });
 
                        if (stopping_ && tasks_.empty()) {
                            return;
                        }
 
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }
 
    ~ThreadPool() {
        {
            std::lock_guard<std::mutex> lock(m_);
            stopping_ = true;
        }
        cv_.notify_all();
 
        for (auto& worker : workers_) {
            if (worker.joinable()) {
                worker.join();
            }
        }
    }
 
    void submit(std::function<void()> task) {
        {
            std::lock_guard<std::mutex> lock(m_);
            tasks_.push(std::move(task));
        }
        cv_.notify_one();
    }
 
private:
    std::mutex m_;
    std::condition_variable cv_;
    std::queue<std::function<void()>> tasks_;
    std::vector<std::thread> workers_;
    bool stopping_ = false;
};
 
int main() {
    ThreadPool pool(4);
 
    for (int i = 0; i < 8; ++i) {
        pool.submit([i] {
            std::cout << "task " << i << '\n';
        });
    }
}

最佳代码实践

  • 线程数通常接近 CPU 核心数,IO 任务可适当更多。
  • 任务队列最好有容量限制,避免生产速度远大于消费速度。
  • 大量小任务要考虑合批,避免入队/唤醒成本超过任务本身。
  • 析构时要明确策略:处理完剩余任务再退出,还是丢弃任务立即退出。
  • 任务内部异常要捕获,否则可能导致工作线程异常终止。
  • 需要返回值时,把任务包装成 packaged_task 并返回 future

常见错误用法

for (int i = 0; i < 100000; ++i) {
    std::thread([i] {
        do_work(i);
    }).detach();
}

问题:线程数量失控。应提交到线程池。

注意事项

  • 上面的线程池是教学版,没有返回值、异常传播、队列容量、任务优先级。
  • 工程中建议使用成熟实现,或至少补齐异常处理和停止策略。
  • 线程池适合 CPU 或阻塞任务;高并发网络服务还会使用事件循环、协程、异步 IO。
  • 想接近高性能运行时的吞吐,线程池必须配合背压、任务粒度控制和低竞争队列。

学习路径