条件变量

内容介绍

条件变量用于线程之间等待和通知。典型场景是生产者消费者:队列为空时消费者睡眠,生产者放入数据后唤醒消费者。

条件变量必须和 std::mutexstd::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_onenotify_all
  • 被唤醒后再次检查条件,不要假设被唤醒就一定有数据。
  • 队列关闭时要通知所有等待者。

常见错误用法

cv.wait(lock);
auto value = q.front();

问题:虚假唤醒或队列仍为空时会出错。

while (q.empty()) {
    // busy wait
}

问题:忙等会浪费 CPU,应使用条件变量阻塞等待。

注意事项

  • notify_one 唤醒一个等待线程,notify_all 唤醒所有等待线程。
  • 条件变量不保存通知,必须用共享状态保存“条件是否成立”。
  • 条件变量是实现 cppChannel模型cpp线程池 的基础。

学习路径