多线程 一个线程等待某种事件发生

背景:某个线程在能够完成其任务之前可能需要等待另一个线程完成其任务。

例如:坐夜间列车,为了能够不坐过站,

1,整夜保持清醒,但是这样你就会非常累,不能够睡觉。

2,如果你知道几点会到你要下车的站,就可以提前定个闹钟,然后睡觉等待闹钟叫醒你,但是如果车中间有延误,闹钟响了,但是还没到你要下次的站;还有一种更恶劣的情况就是,闹钟还没响,但是列车已经过站了。

3,最好的办法就是,快到站前,有个人能把你叫醒。

为了能够达到上面场景3的效果,条件变量(Condition variable)就登场了。

对应上面的3个场景,请看下面的代码。

场景1的代码:

while(某个条件){//这个条件由另一个线程来变更,所以就一直循环来检查这个条件,CPU就得不到休息,浪费系统的性能

}

场景2的代码:

std::unique_lock<std::mutex> lk(m);
while(某个条件){//这个条件由另一个线程来变更,先睡眠一会,等待别的线程变更这个条件,CPU得到了休息,节省了系统的性能
lk.unlock();
sleep(休眠一定的时间);
lk.lock();
}
//缺点:无法准确知道要休眠多长的时间。休眠时间过长就会导致响应过慢,休眠时间过短,醒来发现条件还没被变更,还得继续休眠。

场景3的代码:

#include <iostream>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <thread>
#include <unistd.h>//sleep std::mutex mut;
std::queue<int> data_queue;//-------------------①
std::condition_variable data_cond; void data_preparation_thread(){
int data = 0;
while(true){
data++;
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);//-------------------②
data_cond.notify_one();//-------------------③
std::cout << "after notify_one" << std::endl;
//std::this_thread::sleep_for(1000);
sleep(1);
}
} void data_process_thread(){
while(true){
std::unique_lock<std::mutex> lk(mut);//-------------------④
std::cout << "before wait" << std::endl;
data_cond.wait(lk, []{return !data_queue.empty();});//-------------------⑤
std::cout << "after wait" << std::endl;
int data = data_queue.front();
std::cout << data << std::endl;
data_queue.pop();
lk.unlock();//-------------------⑥
//假设处理数据data的函数process要花费大量时间,所以提前解锁
//process(data);
}
}
int main(){
std::thread t1(data_preparation_thread);
std::thread t2(data_process_thread);
t1.join();
t2.join();
}

1,有一个在多个线程间传递数据的队列①,修改队列前锁定队列,把数据压入队列②,压入完成后通知等待它的线程,说:我已经把数据做好,你们可以使用了③。

2,另一个线程使用队列前,先锁定这个队列④,注意是用std::unique_lock而不是std::lock_guard,理由后面说。

3,data_cond.wait(),检查队列里是否有数据(用的是lambda函数,也可以是普通函数),

  • 如果条件不满足(lambda函数返回false),wait解锁这个互斥元,并将该线程置于阻塞状态,继续等待notify_onde()来唤醒它。
  • 如果条件满足(lambda函数返回true),wait继续锁定这个互斥元,执行wait后面的代码。

这就是为什么使用std::unique_lock而不是std::lock_guard。等待中的线程必须解锁互斥元,并在wait返回true的时候重新锁定这个互斥元,std::lock_guard没有这个功能。如果线程在等待期间不解锁互斥元,把数据压入队列的线程就无法锁定这个互斥元,就无法压入数据,就无法执行notify_one(),所以等待的线程就永远处于等待状态。。。

4,std::unique_lock另外的灵活性,假设得到队列里的数据后,要做一个特别耗时的处理,做这个耗时的处理前就应该解锁这个互斥元⑥,std::unique_lock提供了这个灵活性,而std::lock_guard没有提供这个灵活性。

5,notify_one()后,另一个wait的线程不是马上就被唤醒!!!

github源代码

编译方法:

g++ -g condition_vari-4.1.cpp -std=c++11  -pthread

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

c/c++ 多线程 一个线程等待某种事件发生的更多相关文章

  1. python 多线程、线程锁、事件

    1. 多线程的基本使用 import threading import time def run(num): print('Num: %s'% num) time.sleep(3) if num == ...

  2. MFC多线程各种线程用法 .

    http://blog.csdn.net/qq61394323/article/details/9328301 一.问题的提出 编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleTh ...

  3. c/c++ 多线程 多个线程等待同一个线程的一次性事件

    多线程 多个线程等待一个线程的一次性事件 背景:从多个线程访问同一个std::future,也就是多个线程都在等待同一个线程的结果,这时怎么处理. 办法:由于std::future只能被调用一次get ...

  4. Java多线程:线程与进程

    实际上,线程和进程的区别,在学OS时必然是学习过的,所缺的不过是一些总结. 1. 进程 2. 线程 3. 进程与线程 4. 多进程与多线程对比 5. Java多进程与多线程 5.1. Java多进程 ...

  5. Java多线程(五) —— 线程并发库之锁机制

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...

  6. python多线程与线程

    进程与线程的概念 进程 考虑一个场景:浏览器,网易云音乐以及notepad++ 三个软件只能顺序执行是怎样一种场景呢?另外,假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I ...

  7. 008-多线程-锁-JUC锁-CyclicBarrier【让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行】

    一.概述 “循环栅栏”.大概的意思就是一个可循环利用的屏障. CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).因 ...

  8. Java多线程及线程状态转换

    以下内容整理自:http://blog.csdn.net/wtyvhreal/article/details/44176369 线程:是指进程中的一个执行流程.  线程与进程的区别:每个进程都需要操作 ...

  9. java 多线程以及线程池

    1.多线程可以使程序反应更快,交互性更强,执行效率最高. 2.创建一个线程:  要实现Runnable 接口,创建Thread类的对象,用start开始执行线程. 3.使用Thread中的yield( ...

随机推荐

  1. Python中面向对象的概念(科普)

    面向对象(OOP)基本概念 面向对象编程 —— Object Oriented Programming 简写 OOP 目标 了解 面向对象 基本概念 01. 面向对象基本概念 我们之前学习的编程方式就 ...

  2. nginx 关于client_max_body_size client_body_buffer_size配置

    最近生产环境在这两个参数之间遇到过几次坑.这里记录下.client_max_body_sizeclient_max_body_size 默认 1M,表示 客户端请求服务器最大允许大小,在“Conten ...

  3. qt5 connect问题

    参考资料:Qt学习之路2     在qt从4到5的升级过程中,connect的方法只是添加了一些重载的形式,qt5新增的connect添加了编译器类型检查,如果遇到匹配失败的,或者找不到信号/槽的地方 ...

  4. redis 系列7 数据结构之跳跃表

    一.概述 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树(关系型数据库的索引就是平衡树 ...

  5. 使用Genymotion遇到的坑_Genymotion无法启动_unable to start the virtual device

    Genymotion 十分好用,以下是我使用Genymotion碰到的问题及解决办法.目前没有看到网上有完整的解决. 遇到如下图的问题: 这个问题很多人可能回去百度“the virtual devic ...

  6. 基础才是重中之重~delegate里的Invoke和BeginInvoke

    回到目录 Invoke和BeginInvoke都是调用委托实体的方法,前者是同步调用,即它运行在主线程上,当Invode处理时间长时,会出现阻塞的情况,而BeginInvod是异步操作,它会从新开启一 ...

  7. Python进阶:切片的误区与高级用法

    2018-12-31 更新声明:切片系列文章本是分三篇写成,现已合并成一篇.合并后,修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔接做了大量改动.原系列的单篇就不删除了,毕竟也是 ...

  8. Kali~2018安装后的配置

    今天,物理机上成功的安装了Kali Linux系统,但是要想用的顺手还需要花费许多时间和精力,下面就是我对它的养成之路. 一.添加普通用户 useradd -m -G sudo,video,audio ...

  9. .NET快速信息化系统开发框架 V3.2 -> WinForm“组织机构管理”界面组织机构权限管理采用新的界面,操作权限按模块进行展示

    对于某些大型的企业.信息系统,涉及的组织机构较多,模块多.操作权限也多,对用户或角色一一设置模块.操作权限等比较繁琐.我们可以直接对某一组织机构进行权限的设置,这样设置后,同一组织机构的用户就可以拥有 ...

  10. AppBoxFuture(四). 随需而变-Online Schema Change

      需求变更是信息化过程中的家常便饭,而在变更过程中如何尽可能小的影响在线业务是比较头疼的事情.举个车联网监控的例子:原终端设备上传车辆的经纬度数据,新的终端设备支持同时上传速度数据,而旧的车辆状态表 ...