17370845950

c++中如何使用std::condition_variable实现生产者消费者_c++同步【实例】
std::condition_variable必须与std::mutex配合使用,所有操作须在持有同一把锁的前提下进行;wait()需用while循环防虚假唤醒;notify_one()比notify_all()更高效,适用于典型生产者-消费者场景。

std::condition_variable 必须和 std::mutex 配合使用

单独声明 std::condition_variable 没有意义,它不保存状态,也不提供原子操作。所有 wait()notify_one()notify_all() 调用都必须在持有同一把 std::mutex 的前提下进行,否则行为未定义(常见崩溃或死锁)。

典型错误是:在 wait() 前没加锁,或在 notify

_xxx() 时没锁、或用了不同 mutex —— 这些都会导致程序随机失败,尤其在多核机器上更难复现。

  • wait() 会自动释放传入的 std::unique_lock<:mutex>,并在被唤醒后重新获取锁,所以必须传入已锁定的锁对象
  • notify_one()notify_all() 不要求当前线程持有锁,但为避免竞态(例如通知时消费者刚检查完条件但还没 wait),**强烈建议在持有锁的上下文中调用**
  • 不要用 std::lock_guard 替代 std::unique_lock:前者不可转移、不可手动解锁,无法满足 wait() 的内部解锁需求

必须用 while 循环检查条件,不能用 if

std::condition_variable::wait() 可能被虚假唤醒(spurious wakeup),即没有被 notify 就返回。C++ 标准允许这种行为,且各平台实现均存在。用 if 判断一次就进入临界区,会导致逻辑错乱(比如从空队列取数据)。

正确做法是把条件判断写在 while 循环中,确保每次从 wait() 返回后都重新验证业务条件是否真正满足。

  • 错误写法:if (queue.empty()) cv.wait(lock);
  • 正确写法:while (queue.empty()) cv.wait(lock);
  • 生产者同理:用 while (queue.size() >= capacity) 判断是否满,而非 if

一个可运行的双线程生产者-消费者实例

以下代码用一个固定容量的 std::queue 模拟缓冲区,两个线程分别执行生产和消费,共享一个 std::mutex 和一个 std::condition_variable。注意:所有对 queue 的读写都受同一把锁保护,cv 仅用于阻塞/唤醒协调。

#include 
#include 
#include 
#include 
#include 
#include 

std::queue buffer;
std::mutex mtx;
std::condition_variable cv_producer, cv_consumer;
const size_t CAPACITY = 3;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock lock(mtx);
        while (buffer.size() == CAPACITY) {
            cv_producer.wait(lock); // 等待有空位
        }
        buffer.push(i);
        std::cout << "Produced: " << i << "\n";
        lock.unlock(); // 手动解锁,避免 notify 时还持锁(非必须,但更清晰)
        cv_consumer.notify_one(); // 唤醒一个消费者
    }
}

void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock lock(mtx);
        while (buffer.empty()) {
            cv_consumer.wait(lock); // 等待有数据
        }
        int val = buffer.front();
        buffer.pop();
        std::cout << "Consumed: " << val << "\n";
        lock.unlock();
        cv_producer.notify_one(); // 唤醒一个生产者
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

notify_one() vs notify_all() 的实际影响

在生产者-消费者模型中,通常用 notify_one() 就够了:一个新元素入队,只需唤醒一个等待的消费者;一个元素出队,只需唤醒一个等待的生产者。滥用 notify_all() 会造成“惊群效应”——多个线程同时被唤醒、竞争锁、大部分又立刻回到等待,浪费 CPU 和调度开销。

只有在以下情况才考虑 notify_all()

  • 条件变量关联多个互斥条件(比如同时等待“非空”或“非满”)
  • 无法确定哪个等待线程的条件已满足(如优先级队列场景)
  • 使用 wait_for()wait_until() 且需统一处理超时后的批量清理

多数简单队列场景里,notify_one() 更轻量、更可控。但要注意:如果唤醒的线程因条件不满足又立即 wait,而其他线程本可推进,说明逻辑或唤醒时机有问题 —— 这类问题往往藏在循环条件或 notify 位置里。