原文链接:并发之(条件变量:condition_variable、condition_variable_any)

一、Condition Variable(条件变量)的意图

  • 在前文的文章中,我们有一个演示案例,让某线程等待另一线程,其使用的办法是使用ready flag的方法。代码如下:
bool readyFlag;
std::mutex readyFlagMutex;void thread1()
{//做一些thread2需要的准备工作//...std::lock_guard<std::mutex> lg(readyFlagMutex);readyFlag = true;
}void thread2()
{//等待readyFlag变为true{std::unique_lock<std::mutex> ul(readyFlagMutex);//如果readyFlag仍未false,说明thread1还没有锁定,那么持续等待while (!readyFlag){ul.unlock();std::this_thread::yield();std:this_thread::sleep_for(std::chrono::milliseconds(100));ul.lock();}}//释放lock//在thread1锁定之后,做相应的事情
}
  • 但是上面的代码效率很低,原因有:

  • 标准库提供了条件变量,它是个变量,借助它,一个线程可以唤醒一或多个其他等待中的线程
  • 想要使用条件变量,需要配合mutex、unique_lock使用
    • 因为条件变量也是一种变量,可能会有多个线程对其访问,因此在使用条件变量前,我们需要锁定一个mutex,然后这样才可以做好并发操作
    • 在notify()端:当条件满足时,我们可以调用notify_one()或notify_all()通知wait()等待端条件满足
    • 在wait()端:因为有多个线程需要对条件变量进行访问,因此我们先锁定mutex,此处用的是unique_lock<>锁住muex。然后进行一系列的操作,最终调用wait()等待在条件变量上,等待的时候其他线程可能还需要使用mutex,因此在wait()的内部会调用unique_lock的unlock()函数,在等待的过程中将mutex进行解锁,这样wait()的时候才不会因为mutex永久阻塞而造成其他线程无法继续向前执行
      • 但是不能使用lock_guard,因为等待中的函数有可能锁定或解除mutex,但是lock_guard不提供lock()或unlock()函数,unique_lock()提供lock()或unlock()函数
  • “假醒”的注意事项:
    • “假醒”就是某个条件变量的wait()动作有可能在该条件变量尚未被notified时便返回
    • 以下引自Anthony Williams的[Williams:CondVar]:“假醒无法被测定,以使用者的观点来看它们实质上是随机的。然而它们通常发生于thread library无法可靠确定某个waiting thread不遗漏任何notification时。由于遗漏notification便代表条件变量无用,thread library宁愿在线程的wait之中唤醒它而不愿承受风险”
    • 因此,发生wakeup不一定意味着线程所需要的条件已经掌握了。更确切地说,在wakeup之后你仍然需要代码去验证“条件实际已达成”。因此(例如)我们必须检查数据是否真正备妥,或是我们仍需要诸如ready flag之类的东西。为了假设和查询其他端供应的数据或ready flag,可使用同一个mutex

二、演示案例

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <condition_variable>
using namespace std;bool readyFlag;
std::mutex readyMutex;
std::condition_variable readyCondVar;void thread1()
{std::cout << "thread1 starting..." << std::endl;std::cout << "cin:" << std::endl;std::cin.get();{//锁住条件变量,然后将readyFlag变为truestd::lock_guard<std::mutex> lg(readyMutex);readyFlag = true;}//作用域结束之后,条件变量自动释放//通知其他阻塞在readyCondVar条件变量上的代码readyCondVar.notify_one();std::cout << "thread1 done" << std::endl;
}void thread2()
{std::cout << "thread2 starting..." << std::endl;{std::unique_lock<std::mutex> ul(readyMutex);//等待在readyCondVar条件变量上(参数2见下面介绍)readyCondVar.wait(ul, [] {return readyFlag; });}std::cout << "thread2 done" << std::endl;
}int main()
{auto f1 = std::async(std::launch::async, thread1);auto f2 = std::async(std::launch::async, thread2);this_thread::sleep_for(std::chrono::minutes(1));
}
  • 运行结果
  • 当我们随便输入一个字符之后,线程1先结束,然后thread1()中的条件变量通知thread2()中的wait(),使其返回,然后thread2()也跟着结束

代码解释

  • 在thread1()中(通知者):

    • 我们锁住readyMutex,然后更新readyFlag,之后当作用域结束之后,readyMutex自动被释放
    • 然后通知条件变量readyCondVar
  • 在thread2()中(等待者):
    • 先是用一个unique_lock锁住mutex,然后等待wait条件变量readyCondVar
    • 注意,此处不能使用lock_guard,而必须使用unique_lock,因为wait()内部会明确地对mutex进行解锁和加锁
    • 注意此处wait()有两个参数:第一个参数为unique_lock,第二个参数是一个lambda,用来二次检测条件释放真的满足了,wait()在返回时内部会调用该lambda,然后判断readyFlag的值为true还是false,如果是true才真正的苏醒,否则继续等待
      • wait()有两个重载版本,这个代码中使用的是含有两个参数的版本;还有一个版本只有一个参数
  • 因此thread2()的wait()旁边的代码相当于下面的样子:

三、使用条件变量实现多线程Queue

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <queue>
#include <condition_variable>
using namespace std;std::queue<int> _queue;
std::mutex queueMutex;
std::condition_variable queueCondVar;void provider(int val)
{for (int i = 0; i < 6; ++i){{//锁定mutex,然后向_queue中加入元素std::lock_guard<std::mutex> lg(queueMutex);_queue.push(val + i);}//通知等待在条件变量上的线程queueCondVar.notify_one();//延迟一会儿std::this_thread::sleep_for(std::chrono::milliseconds(val));}
}void consumer(int num)
{while (true){int val;{//先锁住互斥量,然后对条件变量进行操作std::unique_lock<std::mutex> ul(queueMutex);//等待条件变量通知,如果收到通知,并且_queue不为空才真正苏醒返回queueCondVar.wait(ul, [] {return !_queue.empty(); });val = _queue.front();_queue.pop();}std::cout << "consumer: " << num << ": " << val << std::endl;}
}int main()
{auto p1 = std::async(std::launch::async, provider, 100);auto p2 = std::async(std::launch::async, provider, 300);auto p3 = std::async(std::launch::async, provider, 500);auto c1 = std::async(std::launch::async, consumer, 1);auto c2 = std::async(std::launch::async, consumer, 2);std::this_thread::sleep_for(std::chrono::minutes(1));
}

四、细说条件变量

  • 头文件<condition_variable>针对条件变量提供了两个class,分别为condition_variable和condition_variable_any
  • condition_variable
    • condition_variable用来唤醒一个或多个等待在某特定条件上的线程,下面列出了condition_variable提供的操作:
    • 所有等待(wait)某个条件的线程都必须使用相同的mutex,且必须使用unique_lock绑定mutex,并且让wait()等待在unique_lock上,否则会发生不明确的行为

  • 如果无法创建条件变量,构造函数会抛出std::system_error异常并带有差错码resource_unavailable_try_again(相当于POSIX中 errno中的EAGAIN)
  • 条件变量不提供复制和赋值操作
  • wait_...()有两种重载形式:
    • 一种是没有pred参数的:当收到条件变量通知时,直接返回
    • 一种是带pred参数的:当收到条件变量通知时,还需要判断pred条件为true才真正苏醒。这种重载形式是为了防止“假醒”
  • wait_for()和wait_until()的返回值:
    • 不提供pred参数的wait_for()和wait_until()的返回值属于以下枚举类:

      • std::cv_statue::timeout——在指定等待指定的时间之后,还没有收到通知,计时器超时
      • std::cv_statue::no_timeout——在等待过程中收到了通知
    • 提供pred参数的wait_for()和wait_until()的返回值为pred判断式的执行结果
  • notify_all_at_thread_exit(cv,ul)全局函数:
    • 用来在其调用者(线程)退出(exit)时调用notify_all()。为此它暂时锁住对应的lock l,后者必须使用所有等待线程共享的一个mutex
    • 为避免死锁,线程调用notify_all_at_thread_exit()之后应该直接退出(exit)。因此这个调用只是为了在通知waiting thread之前先完成清理工作,而且这个清理工作绝不该造成阻塞

condition_variable_any

  • condition_variable_any提供的操作与condition_variable类似(见上图),但是不提供native_handle()和notify_all_at_thread_exit()
  • condition_variable_any不要求使用std::unique_lock对象当做lock
  • 正如C++标准库所言:如果你使用的lock不是标准mutex类型,或者如果你使用标准mutex类型的一个群unique_lock wrapper并搭配condition_variable_any,那么使用者必须确保实现condition_variable_any实例对象所关联之predicate(判断式)的任何必要同步化
  • 事实上该实例对象必须履行所谓的BasicLockable规定,该规定要求提供同步化的lock()和unlock()函数

C++ 标准库 条件变量:condition_variable、condition_variable_any相关推荐

  1. 标准化条件变量 -- condition_variable

    std::condition_variable是条件变.Linux下使用 Pthread库中的 pthread_cond_*() 函数提供了与条件变量相关的功能.和pthread_cond_*()一样 ...

  2. c语言怎么定义一个条件变量,C++ 条件变量(condition_variable)

    先贴一个condition_variable的讲解:https://en.cppreference.com/w/cpp/thread/condition_variable,很详细也很全面,但是是英文的 ...

  3. 条件变量----condition_variable

    概念 互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段.但单靠互斥量无法实现线程的同步.线程同步是指线程间需要按照预定的先后次序顺序进行的行为.C++11对这种行为也提供了有力的支持 ...

  4. 条件变量(condition_variable)

    一.定义: 1.1.解释: 条件变量是利用线程间共享的变量进行同步的一种机制,是在多线程程序中用来实现"等待–>唤醒"逻辑常用的方法,用于维护一个条件(与是条件变量不同的概念 ...

  5. C++11 condition_variable条件变量用法

    C++11 condition_variable条件变量用法 1 什么是条件变量 2 condition_variable类定义 2.1 wait函数 3 condition_variable用法 3 ...

  6. 调用另一个cpp的变量_再谈条件变量—从入门到出家

    再谈条件变量-从入门到出家 C语言--条件变量 条件变量是在线程中以睡眠的方式等待某一条件的发生: 条件变量是利用线程间共享的全局变量进行同步的一种机制: 一个线程等待"条件变量的条件成立& ...

  7. 对条件变量(condition variable)的讨论

    作者:王东 1.1       什么是条件变量和条件等待? 简单的说: 条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个 ...

  8. boost条件变量使用

    C++ BOOST库 条件变量[多线程通信]机制 笔记 1相关理念 (1)类名 条件变量和互斥变量都是boost库中被封装的类. (2)条件变量 条件变量是thread库提供的一种等待线程同步的机制, ...

  9. 再谈条件变量—从入门到出家

    再谈条件变量-从入门到出家 C语言--条件变量 条件变量是在线程中以睡眠的方式等待某一条件的发生: 条件变量是利用线程间共享的全局变量进行同步的一种机制: 一个线程等待"条件变量的条件成立& ...

最新文章

  1. 弹性网络_理论物理所建立解析模型研究凝胶网络弹性介导的液液相分离现象
  2. idea中文乱码问题
  3. docker latest标签问题
  4. 线程的状态 Thread.State||NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
  5. LiveVideoStack线上交流分享 (十) —— 开源声码器WORLD在语音合成中的应用
  6. 中国智能高清视频监控未来发展趋势
  7. 代码整洁之道,clean code
  8. h5点击后字体加粗出现下边框_人力资源管理论文格式(字体+版式+打印)
  9. 开源一站式移动应用生成平台Jingub系列(0):背景资料介绍
  10. Docker学习总结(20)——Docker 容器实践精华问答集锦
  11. glassfish启动后不能进入部署页面_Spring Boot 热部署
  12. 【MyBatis笔记】02-MyBatis配置SQL打印
  13. [RK3399][Android7.1] 调试笔记 --- 查看当前DDR的工作频率
  14. 文库系统 文库网站建设仿百度文库 道客巴巴 豆丁
  15. Java-基于百度API的图片文字识别(支持中文,英文和中英文混合)【收藏】
  16. 微信小程序保存图片到相册
  17. A Bluescreen By Any Other Color
  18. 20165219 2017-2018-2《Java程序设计》结对编程一 第一周总结
  19. 数据分析师需要学什么?数据分析师必备的7种能力
  20. python制作分布图

热门文章

  1. 10位旅游业大咖,预见数字营销新未来!
  2. mysql community edition是什么_MySQL 社区版本(Community Edition)
  3. 甘超波:NLP发问技巧
  4. HBase基本数据操作详解
  5. ValidateRequest=quot;falsequot; 无效
  6. 表情小作坊2.0上线啦
  7. a12处理器和骁龙855_骁龙 855 上市,A12 还能打吗?
  8. 直接拔出u盘有什么影响
  9. JAVA 语言基础学习
  10. linux上如何创建超链接,GridView中的超链接