条件变量(condition variable)


title: 条件变量
date: 2024-06-28 19:00:00
categories:

  • C++
  • Multi-threaded
    tags: QAQ #

条件变量

条件变量condition variable 是C++11引入的一种同步原语,用于实现线程之间的等待和唤醒机制。它通常与互斥锁(mutex)结合使用,以实现复杂的线程同步和通信。

条件变量允许一个或多个线程等待某个条件满足后再继续执行。它提供了一种线程间的通知机制,使得一个线程可以等待另一个线程发出的信号。

主要成员函数

  1. wait

    • void wait(std::unique_lock<std::mutex>& lock,Predicate pred);

    • 当前线程在获取到锁后调用wait,会释放锁并进入等待状态,直到被其他线程唤醒。

    • wait 导致当前线程阻塞,直到通知条件变量或发生虚假唤醒。可以选择提供 pred 来检测虚假唤醒。

    • 例子:

       std::mutex mtx; 
      std::condition_variable cv; 
      bool ready = false;  
      void wait_for_ready() 
      {     		
          std::unique_lock<std::mutex> lock(mtx);     
          cv.wait(lock, []{ return ready;});     // 线程被唤醒后继续执行 
      } 
      
  2. notify_one

    • void notify_one();

    • 唤醒一个等待该条件变量的线程。

    • 例子:

      void set_ready() 
       {     
       std::unique_lock<std::mutex> lock(mtx);     
       ready = true;    
       cv.notify_one(); 
       } 
      
  3. notify_all

    • void notify_all();

    • 唤醒所有等待该条件变量的线程。

    • 例子:

      void set_ready_for_all() 
      {     
          std::unique_lock<std::mutex> lock(mtx);
          ready = true;     
          cv.notify_all();
      }
      

    使用场景

    条件变量常用于生产者-消费者模型、事件通知等场景。以下是一个简单的生产者-消费者模型示例:

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include <queue>
    
    std::mutex mtx;
    std::condition_variable cv;
    std::queue<int> dataQueue;
    bool done = false;
    
    void producer() {
        for (int i = 0; i < 10; ++i) {
            std::unique_lock<std::mutex> lock(mtx);
            dataQueue.push(i);
            cv.notify_one();
        }
        std::unique_lock<std::mutex> lock(mtx);
        done = true;
        cv.notify_all();
    }
    
    void consumer() {
        while (true) {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, []{ return !dataQueue.empty() || done; });
            while (!dataQueue.empty()) {
                int data = dataQueue.front();
                dataQueue.pop();
                std::cout << "Consumed: " << data << std::endl;
            }
            if (done) break;
        }
    }
    
    int main() {
        std::thread prod(producer);
        std::thread cons(consumer);
    
        prod.join();
        cons.join();
    
        return 0;
    }
    
    

这是一个简单的生产者消费者问题,消费者中通过cv.wait(lock)来阻塞当前线程,表示当前线程阻塞等待当前条件变量满足条件,这里wait需要一个lock参数,wait函数不仅需要等待条件变量,还需要在等待期间释放互斥锁,并在被唤醒后重新获取锁。

  1. 释放锁以避免死锁

    当一个线程调用wait时,它会进入等待状态。如果在等待期间不释放互斥锁,其他线程将无法获取锁,从而无法改变条件变量的状态,这会导致死锁。因此,wait函数在进入等待状态前会释放互斥锁。

  2. 重新获取锁以确保线程安全

    当条件变量被唤醒时,wait函数会重新获取互斥锁,以确保在检查条件变量状态时,线程对共享资源的访问是安全的。

  3. 使用std::unique_lock的灵活性

    std::unique_lock提供了比std::lock_guard更灵活的锁管理功能。它允许在锁定和解锁之间进行更多的控制,例如延迟加锁、提前解锁等。这种灵活性对于条件变量的使用非常重要,因为wait函数需要在内部释放和重新获取锁。

注意事项

  1. 避免虚假唤醒

    什么是虚假唤醒?

    • 条件变量可能会在没有满足条件的情况下被唤醒,这种情况称为虚假唤醒。为了确保线程在条件满足时才继续执行,可以传入一个谓词来反复检查条件。

      std::mutex mtx;
      std::condition_variable cv;
      bool ready = false;
      
      void wait_for_ready() {
          std::unique_lock<std::mutex> lock(mtx);
          cv.wait(lock, []{ return ready; }); // 传入lambda表达式检查条件
          std::cout << "Thread is ready!" << std::endl;
      }
      

      当这个线程被唤醒时,会先进行判断这个谓词是否为true来检查条件是否满足,如果为true则继续执行线程,否则将继续wait

    使用wait时,通常需要在循环中检查条件,以避免虚假唤醒。

    或者直接指定wait的第二个参数,在这个谓词中进行条件判断可以省去循环代码,例如:

  2. 锁的使用wait函数需要传入一个std::unique_lock<std::mutex>对象,而不是std::lock_guard,因为wait会在内部释放并重新获取锁。

  3. 通知顺序notify_onenotify_all应在持有锁的情况下调用,以确保条件变量的状态与互斥锁的状态一致。

其他成员

wait_for阻塞当前线程,直到条件变量被唤醒或在指定的超时持续时间之后 (public member function) (公共成员函数)
wait_until阻塞当前线程,直到条件变量被唤醒或直到达到指定的时间点 (public member function) (公共成员函数)
  • 33
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值