Back to VNotes

C++ / Concurrency

线程池任务队列整理

线程池最容易出问题的地方不是创建线程,而是任务队列、停止条件和锁的边界。

基本模型

主线程接收连接或解析任务,然后把可执行单元放入队列。工作线程阻塞等待条件变量,被唤醒后取任务执行。这个模型简单,但要把“没有任务”和“线程池正在关闭”区分清楚。

条件变量

等待条件应该写成谓词,而不是裸 wait。原因是条件变量可能虚假唤醒,也可能在多个线程竞争后发现队列已经被取空。

cv.wait(lock, [&] {
    return stop || !tasks.empty();
});

停止流程

关闭线程池时,先设置 stop 标记,再 notify_all,最后 join 所有 worker。worker 被唤醒后如果队列为空且 stop 为真,就退出循环。这样可以避免线程永远睡在条件变量上。

边界问题

  • 任务函数抛异常时要在 worker 内部捕获。
  • 队列过长时需要考虑限流或丢弃策略。
  • 不要在持锁状态下执行耗时任务。
  • 析构函数里不要再接收新任务。

任务队列的边界

线程池的核心不只是“开几个线程”,而是队列的边界。队列为空时 worker 等待,队列非空时取任务,关闭时唤醒所有 worker 并让它们有机会退出。

异常处理

worker 执行任务时最好捕获异常。否则某个任务抛出异常可能直接结束线程,线程池表面还在运行,实际工作线程已经少了。

try {
    task();
} catch (const std::exception& e) {
    log_error(e.what());
}