一、为什么需要线程池?
不用线程池:来一个任务就 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()。