• c++ 条件变量使用详解 wait_for wait_unitl 虚假唤醒


    c++ 条件变量使用详解

    std::condition_variable

    • 线程间同步的条件变量类。允许一个或多个线程在满足特定条件之前等待,同时允许其他线程通知、唤醒等待的线程。

    成员函数

    • notify_one:通知一个等待的线程。
    • notify_all:通知所有等待的线程。
    • wait:阻塞当前线程,直到条件变量被唤醒。
    • wait_until:阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点。
    • wait_for:阻塞当前线程,直到条件变量被唤醒,或直到指定时限时长后。

    一般使用方法

    • 对于修改条件后通知其他线程的线程:
      • 获得 std::mutex,通常使用 std::lock_guard;
      • 在成功获得锁后,修改条件;
      • 使用 notify_one 或 notify_all 唤醒等待的线程;
    • 对于等待唤醒的线程:
      • 获得 std::mutex,通常使用 std::unique_lock;
      • 使用 wait,wait_for,wait_until 等待唤醒,此时线程释放互斥,并进入阻塞;
      • 被其他线程唤醒,被虚假唤醒或者等待超时,线程自动重获得互斥,此时应检查条件是否成立,若不成立,则继续等待或者返回;

    虚假唤醒

    • 指线程在等待期间,重新获得互斥,而这一行为却不来源于其他任何线程的通知,则称之为虚假唤醒。
    • 按照 c++ 标准规定,虚假唤醒出现的数量以及比率都不确定。

    notify_one notify_all 使用注意

    • 在唤醒其他线程时,通知线程一般无需持有互斥,因为被唤醒线程醒来后,一旦不能立即获得互斥,将会再次阻塞。

    wait 函数简介

    • 函数原型:

      void wait(std::unique_lock<std::mutex>& lock);
      // 若发生虚假唤醒,wait 之后的代码将会被执行
      
      template<class Predicate>
      void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
      /** 相当于
      while (!pred()) {
          通过检查 pred 返回值,可以避免虚假唤醒
          wait(lock);
      }
      **/ 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • lock:互斥体,必须被当前线程锁定;
      • pred:返回 ​false,则继续等待。其签名为 bool pred();
    • 使当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足 pred。

    wait_until 函数简介

    • 函数原型:

      template<class Clock, class Duration>
      std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
                                const std::chrono::time_point<Clock, Duration>& timeout_time);
      // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout
      // 超时则返回 std::cv_status::timeout
      
      template<class Clock, class Duration, class Pred>
      bool wait_until(std::unique_lock<std::mutex>& lock,
                      const std::chrono::time_point<Clock, Duration>& timeout_time,
                      Pred pred);
      /** 相当于
      while (!pred()) {
          通过检查 pred 返回值,可以避免虚假唤醒
          if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
              return pred();
          }
      }
      return true;
      **/
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • lock:互斥体,必须被当前线程锁定;
      • timeout_time:表示停止等待时间的 std::chrono::time_point 类型对象;
      • pred:返回 ​false,则继续等待。其签名为 bool pred();
    • 使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。

    • 使用注意:时钟最好使用稳定时钟,即计时速率恒定且无法调整的时钟。

    wait_for 函数简介

    • 函数原型:

      template<class Rep, class Period>
      std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
                              const std::chrono::duration<Rep, Period>& rel_time);
      // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout,
      // 超时则返回 std::cv_status::timeout
      
      template<class Rep, class Period, class Predicate>
      bool wait_for(std::unique_lock<std::mutex>& lock,
                    const std::chrono::duration<Rep, Period>& rel_time,
                    Predicate pred);
      // 相当于
      // wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • lock:互斥体,必须被当前线程锁定;
      • rel_time:表示等待所耗的最大时间的 std::chrono::duration 类型对象。rel_time 必须足够小,以在加到 std::chrono::steady_clock::now() 时不溢出;
      • pred:返回 ​false,则继续等待。其签名为 bool pred();
    • 使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。

    • 由于操作系统调度或资源争议,此函数可能阻塞长于 rel_time。

    示例代码

    • 线程1修改变量,线程2等待变量被修改。

      #include 
      #include 
      #include 
      #include 
      
      using namespace std::chrono_literals;
      
      int i = 0;
      bool changed{false};
      std::condition_variable cv;
      std::mutex cv_m;
      
      void wait()
      {
          std::unique_lock<std::mutex> lk(cv_m);
          // std::chrono_literals::100ms, c++14 引入
          if (cv.wait_for(lk, 100ms, [](){return changed;})) {
              printf("finished waiting, i is %d\n", i);
          } else {
              printf("wait timeout, i is %d\n", i);
          }
      }
      
      void notify()
      {
          {
              std::this_thread::sleep_for(50ms);
              std::lock_guard<std::mutex> lk(cv_m);
              i = 10;
              changed = true;
              printf("change finished\n");
          }
          cv.notify_one();
      }
      
      int main()
      {
          std::thread t1(notify);
          std::thread t2(wait);
          t1.join();
          t2.join();
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
  • 相关阅读:
    深度学习之训练过程中特征图的可视化
    常见的Web安全漏洞有哪些,Web安全漏洞常用测试方法介绍
    异地监控如何实现远程访问?贝锐蒲公英无需公网IP即可实现
    【Linux】基本指令(一)
    【南京大学jyy操作系统】(三)理解并发程序执行 | 并发控制 : 互斥
    北京十大律师事务所排名前十名(8月最新发布)
    华为机试真题 C++ 实现【运维日志排序】
    【小黑嵌入式系统第四课】嵌入式系统硬件平台(二)——I/O设备、通信设备(UART&USB&蓝牙)、其他(电源&时钟&复位&中断)
    Java中常见锁的分类及概念分析
    程序员的数学课06 向量及其导数:计算机如何完成对海量高维度数据计算?
  • 原文地址:https://blog.csdn.net/luohaha66/article/details/134251052