• 【C++多线程】条件变量condition_variable


    面向的问题

      当一个线程等待另一个线程完成任务时,它会有很多选择。

    • 第一,它可以持续的检查共享数据标志(用于做保护工作的互斥量),直到另一线程完成工作时对这个标志进行重设。不过,就是一种浪费:线程消耗宝贵的执行时间持续的检查对应标志,并且当互斥量被等待线程上锁后,其他线程就没有办法获取锁,这样线程就会持续等待。
    • 第二个选择l是周期轮询,在等待线程在检查间隙,使用 std::this_thread::sleep_for() 进行周期性的间歇在这个循环中,在休眠前,函数对互斥量进行解锁,并且在休眠结束后再对互斥量进行上锁,所以另外的线程就有机会获取锁并设置标识。
    • 第三个选择(也是优先的选择)是,使用C++标准库提供的条件变量condition_variable去等待事件的发生。通过另一线程触发等待事件的机制是最基本的唤醒方式。

    condition_variable

      std::condition_variable 和 std::condition_variable_any 。这两个实现都包含在 <mutex> 或者<condition_variable>头文件的声明中。两者都需要与一个互斥量一起才能工作(互斥量是为了同步);std::condition_variable仅限于与 std::mutex 一起工作,而后者可以和任何满足最低标准的互斥量一起工作,从而加上了_any的后缀。因为 std::condition_variable_any 更加通用,这就可能从体积、性能,以及系统资源的使用方面产生额外的开销。

     wait()和notify_one()

    1 std::mutex mymutex1;
    2 std::unique_lock<std::mutex> sbguard1(mymutex1);
    3 std::condition_variable condition;
    4 condition.wait(sbguard1, [this] {if (!msgRecvQueue.empty())
    5                                     return true;
    6                                 return false;
    7                                 });
    8//没有第二个参数时
    9 condition.wait(sbguard1);

       wait()用来等一个事件或者条件满足,如果第二个参数(可调对象)的lambda表达式返回值是false,即条件不满足,那么wait()将解锁互斥量,并阻塞到本行,如果第二个参数的lambda表达式返回值是true,那么wait()直接返回并继续执行。

      阻塞到什么时候为止呢?阻塞到其他某个线程调用notify_one()成员函数唤醒为止

      如果没有第二个参数,那么效果跟第二个参数lambda表达式返回false效果一样

      wait()将解锁互斥量,并阻塞到本行,直到到其他某个线程调用notify_one()成员函数为止。

      当其他线程用notify_one()将本线程wait()唤醒后,这个wait被唤醒后

      1、wait()不断尝试获取互斥量锁,如果获取不到那么流程就卡在wait()这里等待获取,如果获取到了,那么wait()就继续执行,获取到了锁

      2.1、如果wait有第二个参数就判断这个lambda表达式。

        a)如果表达式为false,那wait又对互斥量解锁,然后又休眠,等待再次被notify_one()唤醒
        b)如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥量已被锁住)。
      2.2、如果wait没有第二个参数,则wait返回,流程走下去。

      流程只要走到了wait()下面则互斥量一定被锁住了。

     1 #include <thread>
     2 #include <iostream>
     3 #include <list>
     4 #include <mutex>
     5 using namespace std;
     6  
     7 class A {
     8 public:
     9     void inMsgRecvQueue() {
    10         for (int i = 0; i < 100000; ++i) 
    11         {
    12             cout << "inMsgRecvQueue插入一个元素" << i << endl;
    13 
    14             std::unique_lock<std::mutex> sbguard1(mymutex1);
    15             msgRecvQueue.push_back(i); 
    16             //尝试把wait()线程唤醒,执行完这行,
    17             //那么outMsgRecvQueue()里的wait就会被唤醒
    18             //只有当另外一个线程正在执行wait()时notify_one()才会起效,否则没有作用
    19             condition.notify_one();
    20         }
    21     }
    22  
    23     void outMsgRecvQueue() {
    24         int command = 0;
    25         while (true) {
    26             std::unique_lock<std::mutex> sbguard2(mymutex1);
    27             // wait()用来等一个东西
    28             // 如果第二个参数的lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行
    29             // 阻塞到什么时候为止呢?阻塞到其他某个线程调用notify_one()成员函数为止;
    30             //当 wait() 被 notify_one() 激活时,会先执行它的 条件判断表达式 是否为 true,
    31             //如果为true才会继续往下执行
    32             condition.wait(sbguard2, [this] {
    33                 if (!msgRecvQueue.empty())
    34                     return true;
    35                 return false;});
    36             command = msgRecvQueue.front();
    37             msgRecvQueue.pop_front();
    38             //因为unique_lock的灵活性,我们可以随时unlock,以免锁住太长时间
    39             sbguard2.unlock(); 
    40             cout << "outMsgRecvQueue()执行,取出第一个元素" << endl;
    41         }
    42     }
    43  
    44 private:
    45     std::list<int> msgRecvQueue;
    46     std::mutex mymutex1;
    47     std::condition_variable condition;
    48 };
    49  
    50 int main() {
    51     A myobja;
    52     std::thread myoutobj(&A::outMsgRecvQueue, &myobja);
    53     std::thread myinobj(&A::inMsgRecvQueue, &myobja);
    54     myinobj.join();
    55     myoutobj.join();
    56 }

      上面的代码可能导致出现一种情况:因为outMsgRecvQueue()与inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在msgRecvQueue 中已经有了很多消息,但是,outMsgRecvQueue还是被唤醒一次只处理一条数据。这时可以考虑把outMsgRecvQueue多执行几次,或者对inMsgRecvQueue进行限流。

    notify_all()

      同时通知所有等待线程,但是需要注意的是,如果所有线程只有一个线程可以拿到互斥量,那么也只有一个线程可以继续执行。

      对使用读写锁的多个读线程,可以同时被唤醒并同时继续工作。

    拓展

      当等待一个一次性事件时,condition_variable显然不是最好的选择,这时需要的是future。

  • 相关阅读:
    新标准C++程序设计读书笔记_继承和多态
    新标准C++程序设计读书笔记_运算符重载
    新标准C++程序设计读书笔记_类和对象
    关于在linux中使用图形界面的网络管理工具
    桌面显卡天梯图和桌面cpu天梯图
    关于linux下关于ssd的使用
    archlinux中c语言的rpc编程
    关于eclipse的一些配置
    利用wireshark抓包获取cookie信息
    笔记本中的archlinux调节亮度
  • 原文地址:https://www.cnblogs.com/chen-cs/p/13236638.html
Copyright © 2020-2023  润新知