C++ 条件变量的用法

C++ 条件变量的用法

在 C++ 中,条件变量(std::condition_variable)用于线程间的同步,通常搭配 std::mutexstd::unique_lock 以实现线程的等待和通知机制。主要的用途包括生产者-消费者模型线程同步等。

基本用法

#include <iostream>

#include <condition_variable>
#include <thread>
#include <mutex>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void consumer()
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] () { return ready; });
    // or: cv.wait(lock, [] { return ready; });
    std::cout << "Consumer Thread start running" << std::endl;
}

void worker()
{
    std::this_thread::sleep_for(std::chrono::seconds(2));   //模拟生产耗时操作
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();   // 唤醒一个等待的线程
}

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

代码详解:

void consumer()
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });
    std::cout << "Consumer Thread start running" << std::endl;
}
// 1. 进入consumer,当前线程加锁mtx,其他线程不能修改ready;
// 2. 执行cv.wait(lock, predicate) 语句;
	// a. 若lambda表达式 [] {return ready} 返回 true,则cv.wait()不会进入等待, 直接返回,consumer线程继续执行后续语句;函数退回后,释放mtx锁;
	// b. 若lambda表达式 [] {return ready} 返回 false,cv.wait()释放mtx锁,并进入等待(阻塞)状态,其他线程可以更新ready状态,并调用cv.notify_one()或cv.notify_all()后,唤醒consumer线程,consumer重新获取mtx锁,再次重复执行步骤2;



// cv.wait(lock, predicate)
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] () { return ready; });
// 等价于:
std::unique_lock<std::mutex> lock(mtx);
while (!ready) {  // 等待条件变量
    cv.wait(lock);  // 释放锁,并等待 notify
}

notify_one() 和 notify_all()

当有多个线程等待条件变量时,notify_one() 只能唤醒一个线程,而 notify_all() 会唤醒所有线程。

#include <iostream>
#include <vector>

#include <condition_variable>
#include <thread>
#include <mutex>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void consumer(int id)
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] () { return ready; });
    std::cout << "Consumer Thread id[" << id << "] start running" << std::endl;
}

void worker()
{
    std::this_thread::sleep_for(std::chrono::seconds(2));   //模拟生产耗时操作
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    // cv.notify_one();   // 唤醒一个等待的线程
    cv.notify_all();   // 唤醒所以等待的线程
}

int main()
{
    std::vector<std::thread> consumers;
    for (int i = 0; i < 3; i++)
    {
        consumers.emplace_back(consumer, i);    //启动多个消费者线程
    }

    std::thread t1(worker);     // 启动生产者线程

    for (auto &consumer_thread : consumers)
    {
        consumer_thread.join();
    }

    t1.join();
    
    return 0;
}

解释:

  • consumer 线程等待 cv.wait(lock, [] { return ready; }),被 notify_all() 唤醒后继续执行。
  • notify_all() 适用于广播通知多个线程。

生产者-消费者模型

条件变量常用于生产者-消费者模型,确保生产者和消费者协同工作。

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
bool finished = false;

void producer() {
    for (int i = 1; i <= 5; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            dataQueue.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    {
        std::lock_guard<std::mutex> lock(mtx);
        finished = true;
    }
    cv.notify_all();
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !dataQueue.empty() || finished; });

        while (!dataQueue.empty()) {
            int data = dataQueue.front();
            dataQueue.pop();
            std::cout << "Consumed: " << data << std::endl;
        }

        if (finished) break;
    }
}

int main() {
    std::thread prod(producer);
    std::thread cons(consumer);

    prod.join();
    cons.join();
    return 0;
}

解释:

  • 生产者不断向 dataQueue 添加数据,并 notify_one() 唤醒消费者。
  • 消费者等待 cv.wait() 直到队列非空,消费数据后继续等待。
  • finished = true 时,消费者退出。

条件变量适用于任务同步线程间通讯,尤其适用于生产者-消费者模型等并发场景。

问题:

1. 多个消费者线程wait时,notify_one() 是随机唤醒一个线程,还是第一个进入等待的线程;

在 C++ 的 std::condition_variable 中,notify_one() 唤醒的线程是随机的,并不是严格按照等待顺序(FIFO)唤醒的。具体而言:

notify_one() 的行为

当多个线程在 cv.wait(lock, predicate) 处阻塞时,调用 cv.notify_one() 随机唤醒其中一个线程,具体行为取决于操作系统的线程调度策略和 C++ 标准库的实现:

  • 可能唤醒最早等待的线程(FIFO 方式),但没有保证
  • 也可能唤醒最近进入等待的线程(LIFO 方式,取决于底层实现)。
  • 线程调度器可能决定选择哪个线程,导致不确定性。

🔹 结论notify_one() 不会严格按照进入 wait() 的顺序唤醒线程,而是随机选择一个线程

2. std::lock_guard 和 std::unique_lock 两种锁

特性std::lock_guardstd::unique_lock
是否默认加锁✅ 是✅ 是
是否默认解锁(作用域外 / 对象被析构)✅ 是✅ 是
是否可手动 unlock()❌ 不能手动解锁✅ 可 unlock()
是否可手动 lock()❌ 不能手动加锁✅ 可 lock()
是否可用于 condition_variable❌ 不能✅ 适用
是否可移动❌ 不能移动✅ 可移动
是否更轻量✅ 轻量级❌ 略有开销

为什么作用域结束会自动释放锁?这是因为:

  1. std::lock_guardstd::unique_lock 是 RAII(资源获取即初始化)机制的对象
  2. 当它们的生命周期结束时,析构函数会自动调用 mutex.unlock(),确保锁被释放。

推荐使用

  • 短临界区std::lock_guard
  • 需要解锁 / 重新加锁 / condition_variablestd::unique_lock

🔹 简单来说

  • std::lock_guard 是更快、更轻量级的锁
  • std::unique_lock 是更灵活、更强大的锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值