线程池
内容介绍
线程池用固定数量线程处理大量任务,避免频繁创建和销毁线程。它是 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。
- 想接近高性能运行时的吞吐,线程池必须配合背压、任务粒度控制和低竞争队列。
学习路径
- 上一节:cppWaitGroup模型
- 当前阶段:cpp多线程
- 下一节:cpp协作取消