条件变量
内容介绍
条件变量用于线程之间等待和通知。典型场景是生产者消费者:队列为空时消费者睡眠,生产者放入数据后唤醒消费者。
条件变量必须和 std::mutex、std::unique_lock 配合使用。
生产者消费者
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
int main() {
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
bool done = false;
std::thread producer([&] {
for (int i = 0; i < 5; ++i) {
{
std::lock_guard<std::mutex> lock(m);
q.push(i);
}
cv.notify_one();
}
{
std::lock_guard<std::mutex> lock(m);
done = true;
}
cv.notify_one();
});
std::thread consumer([&] {
while (true) {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&] {
return !q.empty() || done;
});
if (q.empty() && done) {
break;
}
int value = q.front();
q.pop();
lock.unlock();
std::cout << value << '\n';
}
});
producer.join();
consumer.join();
}为什么 wait 要带谓词
cv.wait(lock, [&] { return !q.empty() || done; });原因:
- 可能发生虚假唤醒。
- 通知可能早于等待发生。
- 多个消费者被唤醒后,条件可能被其他线程抢先改变。
最佳代码实践
- 总是使用
cv.wait(lock, predicate)。 - 修改共享条件时持锁,修改后再
notify_one或notify_all。 - 被唤醒后再次检查条件,不要假设被唤醒就一定有数据。
- 队列关闭时要通知所有等待者。
常见错误用法
cv.wait(lock);
auto value = q.front();问题:虚假唤醒或队列仍为空时会出错。
while (q.empty()) {
// busy wait
}问题:忙等会浪费 CPU,应使用条件变量阻塞等待。
注意事项
notify_one唤醒一个等待线程,notify_all唤醒所有等待线程。- 条件变量不保存通知,必须用共享状态保存“条件是否成立”。
- 条件变量是实现 cppChannel模型 和 cpp线程池 的基础。
学习路径
- 上一节:cpp互斥锁与RAII
- 当前阶段:cpp多线程
- 下一节:cpp原子操作