C++11 condition_variable虚假唤醒异常:生产者消费者代码排查
看起来你在写生产者消费者模型时踩了condition_variable虚假唤醒的坑——这个问题在C++11里很常见,核心原因是wait()可能在没有收到通知的情况下被系统唤醒,如果只用if判断条件就会直接继续执行,导致逻辑错误。我来帮你修正代码,同时解释关键的注意点:
首先,先看修正后的完整代码:
#include <iostream> #include <thread> #include <condition_variable> #include <mutex> #include <chrono> #include <queue> using namespace std; condition_variable cond_var; mutex m; int main() { int c = 0; bool done = false; cout << boolalpha; queue<int> goods; // 生产者线程 thread producer([&](){ for (int i = 0; i < 10; ++i) { // 用unique_lock自动管理锁,比手动lock/unlock更安全 unique_lock<mutex> lock(m); goods.push(i); c++; cout << "生产者生产了: " << i << ",当前队列大小: " << goods.size() << endl; // 通知一个消费者 cond_var.notify_one(); // 这里lock会在作用域结束时自动解锁 } // 生产完成后设置done标志,并通知所有消费者 unique_lock<mutex> lock(m); done = true; cond_var.notify_all(); }); // 消费者线程 thread consumer([&](){ while (true) { unique_lock<mutex> lock(m); // 用while循环检查条件,解决虚假唤醒! // 每次wait返回后都重新确认:队列不为空 或者 生产已完成 cond_var.wait(lock, [&](){ return !goods.empty() || done; }); // 如果生产完成且队列为空,退出循环 if (done && goods.empty()) { break; } // 消费数据 int item = goods.front(); goods.pop(); c--; cout << "消费者消费了: " << item << ",当前队列大小: " << goods.size() << endl; } cout << "消费者退出" << endl; }); producer.join(); consumer.join(); cout << "最终c的值: " << c << endl; return 0; }
接下来解释几个关键的修复点:
用
std::unique_lock替代手动锁管理:
直接调用m.lock()和m.unlock()很容易因为异常或者代码分支遗漏导致死锁,而unique_lock会在作用域结束时自动释放锁,同时condition_variable::wait()必须搭配unique_lock使用(因为wait过程中会临时释放锁,被唤醒后重新获取)。用while循环替代if判断条件(核心!):
这是解决虚假唤醒的关键。虚假唤醒时,wait()会在没有收到生产者通知的情况下返回,如果只用if,消费者会直接尝试消费空队列,导致错误。而while循环会在每次wait返回后重新检查条件,确保队列确实有数据或者生产已经完成,只有条件满足才会继续执行。正确处理结束标志
done:
生产者完成所有生产后,需要设置done标志并调用notify_all()(避免消费者还在wait中阻塞),消费者在检查到done且队列为空时才会退出,确保所有生产的产品都被消费完毕。通知的时机:
生产者在生产完一个数据后调用notify_one(),唤醒一个等待的消费者。注意这里不需要刻意在解锁后通知,因为unique_lock在notify后会自动解锁,不过如果是性能敏感场景,解锁后通知可能更高效(避免消费者被唤醒后还需要等待锁)。
这样修改后,你的代码就能正确处理虚假唤醒问题,稳定运行生产者消费者模型了。
内容的提问来源于stack exchange,提问作者tesla1060

