C++中std::condition_variable正确使用及Worker类故障排查
问题原因分析
1. 任务被覆盖,无任务队列
你的Worker仅用单个std::function<void()> task存储任务,每次调用push都会覆盖之前的任务。比如循环push 10次时,只有最后一次的任务(打印9)会被保留,前面的任务全部被丢弃。
2. 未处理条件变量的虚假唤醒
new_task.wait(lock)未使用带谓词的版本,线程可能被虚假唤醒(并非因为调用notify_one),此时stop为false,就会重复执行当前的task,这也是单次push会无限打印"Hello"的核心原因。
3. 析构函数未正确终止线程
Worker的析构函数为空,当main函数结束时,Worker对象销毁,其内部的std::thread对象在析构时仍处于joinable状态,C++标准会直接调用std::terminate终止程序,这就是你看到terminate called without an active exception的原因。
4. 任务执行时持锁阻塞提交
线程执行task()时仍持有锁,导致后续push操作无法获取锁,任务提交被阻塞;同时执行完任务后没有正确重置流程,进一步加剧了重复执行的问题。
修正后的代码
#include <thread> #include <mutex> #include <condition_variable> #include <functional> #include <atomic> #include <iostream> #include <queue> class Worker { public: Worker(Worker&&) = delete; Worker(const Worker&) = delete; Worker() : stop(false) , thread(&Worker::worker_thread, this) {} void push(std::function<void()> _f) { std::unique_lock lock(thread_mutex); tasks.push(std::move(_f)); new_task.notify_one(); } ~Worker() { // 标记停止并唤醒线程 { std::unique_lock lock(thread_mutex); stop = true; } new_task.notify_one(); // 等待线程安全终止 if (thread.joinable()) { thread.join(); } } private: void worker_thread() { std::unique_lock lock(thread_mutex); while (true) { // 仅在有任务或需要停止时唤醒 new_task.wait(lock, [this]() { return stop || !tasks.empty(); }); if (stop && tasks.empty()) { // 停止且无剩余任务,退出线程 return; } // 取出任务后释放锁,避免阻塞push auto task = std::move(tasks.front()); tasks.pop(); lock.unlock(); // 执行任务 task(); // 重新获取锁,进入下一轮等待 lock.lock(); } } std::atomic<bool> stop; std::queue<std::function<void()>> tasks; // 任务队列存储多个任务 std::thread thread; std::mutex thread_mutex; std::condition_variable new_task; }; // 测试代码 int main() { Worker t; t.push([] { std::cout << "Hello from worker" << std::endl; }); for (int i = 0; i < 10; ++i) t.push([i] { std::cout << i << std::endl; }); return 0; }
修正点说明
- 任务队列:改用
std::queue存储任务,确保所有提交的任务都能被依次执行,不会被覆盖。 - 带谓词的wait:通过谓词确保线程仅在有任务或需要停止时被唤醒,彻底避免虚假唤醒导致的重复执行。
- 安全终止线程:析构函数中先标记停止状态,唤醒线程后等待线程执行完剩余任务再join,避免程序异常终止。
- 执行任务时解锁:取出任务后立即释放锁,让
push操作可以随时提交任务,不会被任务执行过程阻塞。
内容的提问来源于stack exchange,提问作者ugo_capeto
相关产品推荐
相关产品推荐

