线程安全任务队列需封装 std::queue、std::mutex、std::condition_variable 和 shutdown_flag;push 须加锁并 notify_one,wait_and_pop 需用带谓词的 wait 检查非空与关闭状态,工作线程循环调用 wait_and_pop 并依返回值退出,C++20 可用 std::jthread 简化生命周期管理但不替代锁。

如何用 std::queue + std::thread 实现线程安全的任务队列
直接用裸 std::queue 会崩——多个线程同时 push 或 pop 时,没有锁保护就会触发数据竞争。必须配 std::mutex 和 std::condition_variable 来协调生产与消费。
典型结构是封装一个类,内部持有一个 std::queue、一个 std::mutex、一个 std::condition_variable,以及一个 bool 类型的 shutdown_flag 控制退出。
关键点:
-
push()要加锁,插入后调用notify_one()唤醒等待中的工作线程 -
wait_and_pop()(或类似阻塞取任务函数)需用wait()配合谓词:检查队列非空且未关闭,避免虚假唤醒 - 析构时要设
shutdown_flag = true并调用notify_all(),确保所有工作线程能退出循环
为什么不能只用 std::condition_variable::wait() 不带谓词
裸调用 wait(cv, mtx) 没有检查条件,线程可能被虚假唤醒后发现队列还是空的,接着 pop() 就会调用 front() 或 pop() 在空队列上——触发 std::out_of_range 或未定义行为。
立即学习“C++免费学习笔记(深入)”;
正确写法是:
std::unique_lock<std::mutex> lock(mtx_);
cv_.wait(lock, [this] { return !queue_.empty() || shutdown_; });
注意谓词里同时检查 !queue_.empty() 和 shutdown_:前者保证有活干,后者保证能干净退出。
常见错误:
- 只检查
!queue_.empty(),没管shutdown_→ 工作线程无法响应停止信号 - 在
wait()外再加一层if (queue_.empty()) continue;→ 竞态窗口仍在,不可靠
工作线程怎么写才不会卡死或重复执行
每个工作线程应运行一个无限循环,但退出条件必须由队列控制,不能靠外部变量轮询。
标准模式是:
while (true) {
auto task = queue_.wait_and_pop(); // 阻塞直到有任务或 shutdown
if (!task && shutdown_) break; // 取不到任务且已要求关闭 → 退出
if (task) task();
}
这里 wait_and_pop() 返回 std::optional<std::function<void()>> 是推荐做法,比返回裸指针或抛异常更清晰。
容易踩的坑:
- 用
queue_.front(); queue_.pop();分两步操作 → 中间可能被其他线程插入/清空,导致front()无效 - 忘记在
push()后调用notify_one()→ 新任务永远没人处理 - 多个工作线程共用一个队列但没统一 shutdown 逻辑 → 部分线程还在等,程序无法结束
std::jthread 能简化 shutdown 吗?什么时候该用
C++20 的 std::jthread 自带协作式中断机制,配合 std::stop_token 可以替代手写的 shutdown_flag,但要注意它不自动保护共享数据——队列本身仍需锁。
如果你用 C++20 且希望语义更清晰,可以这样改:
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
auto task = queue_.wait_and_pop(stoken); // 需自己实现支持 stop_token 的版本
if (task) task();
}
}
不过目前标准库没给 std::queue 加 stop_token 支持,所以 wait_and_pop 还是要自己封装;std::jthread 主要帮你省掉 join() 调用和手动管理 flag,不是银弹。
实际建议:
- 新项目、C++20 环境 → 用
std::jthread+ 自定义带 token 的等待逻辑,可读性更好 - 需要兼容 C++11/14/17 → 老实写
shutdown_flag+condition_variable,更可控 - 别以为用了
std::jthread就不用锁了——它只管线程生命周期,不管数据竞争
最常被忽略的是任务对象的拷贝/移动语义:如果任务里捕获了局部变量,又在线程外提前销毁,就会 dangling capture。务必确认 std::function 存储的是值语义的对象,或者显式用 std::shared_ptr 延长生命周期。


















