219-C++多线程(条件变量)
操作1(不加线程)
操作2(引入多线程)
打印的结果不是我们想要的!
这里造成结果混乱的原因是什么?
原因是: 并发执行,多线程抢占资源。
这是一种异步执行方案。当我们程序链接编译通过之后,我们从主函数开始执行,我们将分配相应的内存区域(用户空间区):代码区,数据区,堆区,栈区。主函数运行,主线程产生,我们在这里定义一个数组,定义5个对象,但是这些对象没有被初始化,不是我们真正意义上的线程对象,是不可被执行。我们在for循环中,依次初始化线程对象,分配相对应的栈帧空间和线程信息。建立这5个线程之后,这5个线程的执行顺序我们不能控制。我们不知道谁会先执行。5个线程是共享同一个屏幕。先调动哪个线程,哪个线程先打印到屏幕。也有可能是第一个线程调动执行,有可能在输出字符A的时候时间片到了,从执行变为就绪,或者因为其他什么原因,变为阻塞了。其他的线程调动执行先打印到屏幕上了。
所以打印的是乱序!
操作3(对多线程进行添加互斥量)
我们希望这5个线程在打印时,不进行相互侵占。
这样执行,会不会出现打印
这种情况?
答案是不会的!因为锁只有一把!!!共享的是同一把锁,如果一个线程获得锁,全部打印之后才结束释放锁,其他线程在此期间获取不到锁,阻塞着。
运行程序!
仍然是异步方向,只是我们加锁了。同步方式是要有次序感!!!
操作4(改变加锁解锁的位置)
这种就会出现打印下面这种情况(这种可能性是有的)(其他线程可能会抢占到这把锁)
操作5(改变加锁解锁的位置)
有可能出现
体现加锁的粒度!
锁的粒度越小,并发性越强,耗的资源越多!(都要加锁,都要消耗资源)
我们在加锁,可能老忘记释放锁,我们用锁的管理来处理(相当于智能指针的概念),唯一性锁,当我们进入函数,要创建对象,调动构造函数,构造函数里面我们调动g_mtx.lock(),加锁,函数结束,从函数退出,局部对象的生存期到了,调动析构函数,析构函数里面有解锁操作。
如果加的是普通锁,第一个线程调动funa,加锁。第二个线程调动funa,就阻塞住。递归锁:只要我这个函数获得锁,不管调动多少次,都可以获得锁,执行下去。
条件变量
条件变量是进行线程同步的有利工具!
生产者和消费者例子(条件变量和互斥量)
我们先来看看
我们让生产者生产10次,消费者消费10次。
我们要让线程同步执行。
无论生产者在前还是消费者在前,让生产者先生产数据,消费者再打印数据,打印完告诉生产者已经取得一个数据,生产者再接着再生产数据。我们要求生产者和消费者有一定的次序。
单纯靠互斥量,我们完成不了这个事情。
我们所谓的互斥量,只能进行互斥,只能互斥生产者生产的时候消费者不能取,消费者取的时候生产者不能生产。不能控制先生产再消费。
我们必须要引入条件变量!
我们分析一下这段代码。
cv.wait();的责任:1.把当前线程挂起2.释放互斥量mtx的拥有权(解锁)
我们有3个状态:就绪态,执行态,阻塞态。
我们拿p当做生产者线程,s当做消费者线程。
如果说我们的生产者线程p被调用,p从就绪态跑到执行态。p运行的时候,进入函数,获得锁,如果这个锁没有被其他线程获得,p就获得锁,进入到for循环,条件变量获得这个锁,等待函数把这个生产者线程挂起到阻塞队列中,是由条件变量标识的阻塞队列,并且释放互斥锁的拥有权。
锁释放掉了。我们的s消费者线程,开始执行程序,由就绪态到执行态,这个锁已经被释放,s线程获得锁,进入for循环,处于等待,把消费者s线程挂起到阻塞态,释放锁的拥有权。现在p和s线程都挂起,锁释放掉了。我们得看能否收到一个唤醒(条件变量的消息通知),我们把线程唤起,如果没有消息通知,这两个线程一直在阻塞态,形成死锁。但是形成死锁很困难,因为有虚假唤醒。条件变量上可能会有其他的人发起唤醒。
我们运行程序
处于互相地等待中。处于死锁的过程。
我们增加代码,更易观察。
运行程序
我们看见生产者线程在运行,消费者线程在运行。
我们采取虚假唤醒一下。
出现这个情况的原因是我们得让主线程先睡眠一下。
如果想达到线程的同步,要互斥量和条件变量,还有全局变量!
我们现在有3种状态:就绪,执行,阻塞。
我们创建了2个线程:生产者线程,消费者线程。
我们程序走的时候,比如说,我们消费者先运行,消费者从就绪到达执行状态,打印消费者线程运行的字符串,然后获得锁,向下走,进入到for循环,if为真,进入if内部,执行wait,把当前线程从执行态放入到阻塞态,把线程挂起在条件变量的阻塞队列上,第二步把互斥锁释放掉。我们的消费者阻塞了,生产者开始运行了,从就绪态到达执行态,生产者线程向下执行,这个锁被消费者释放了,生产者获得锁,if的变量为假,不执行if,进行生产,并且把值改为真。并且打印生产者生产了的字符串,然后进行唤醒,把阻塞队列的消费者唤醒了,这种可能性有很多:我们的消费者到达了就绪态,但是有可能我们的生产者从运行跳到阻塞,有可能我们的生产者继续运行。
如果我们的第一种情况,我生产者再进入循环,if的变量为真,进入if,进行了wait,阻塞当前线程(生产者),释放锁。现在我们的消费者已经唤醒了,从等待函数返回,它再一次获取互斥量!!!获得锁,如果互斥量的拥有权仍然是别人拥有,会产生异常。我们打印消费了一个值,由真变为假,又进行了一次唤醒,把生产者唤醒。我们的消费者再执行循环,为假了,求反为真,进入if,执行wait,把消费者变为阻塞态,阻塞在条件变量,并且释放了锁。我们的生产者到达了就绪态,获取锁,继续执行下去!
如果第一次执行,创建线程,p生产者线程和s消费者线程都就绪了,我们生产者从就绪态到达运行态,执行程序,为假,不执行wait,进行生产,把全局变量变为真,接着进行唤醒,实际上没有人阻塞,回过来进行生产,全局变量为真,进入if,把自己阻塞,释放互斥锁,消费者开始执行,获取锁,进入到循环,if的变量为假,不进入if,进行消费,把全局变量变为假,唤醒在条件变量上阻塞的生产者p线程,p到达就绪态。我们的消费者回到循环,if的变量为真,进入if,把自己阻塞到条件变量的阻塞队列,释放锁,生产者p被唤醒,从阻塞到就绪态,获取锁,执行,进行生产,把全局变量变为真,唤醒阻塞的s消费者线程,再回到循环,为真,进入if,执行wait,把自己(生产者)阻塞,释放锁。s消费者进入运行态,获取锁。以此类推。保持了原子性。
我们运行程序!
这就是一种同步!!!
但是如果我们不把主函数的唤醒代码屏蔽掉的话。
程序处于不确定状态。
有可能打印乱序。
虚假唤醒:当我们先执行生产者线程,if为真,把生产者线程阻塞,释放锁,应该被消费者唤醒。消费者阻塞应该被生产者唤醒。但是有可能被其他线程如主线程唤醒。
如何解决虚假唤醒???
把生产者和消费者的if改为while就可以解决虚假唤醒了!!!
如果这个条件为假,当我们被唤醒,也为假,只有生产完才为真,如果中途被唤醒,则就是虚假的唤醒!
第二个例子(给队列,进行生产和消费)
最多生产5个,满了不能生产。
运行正确!
惊群现象
假如我们的很多线程都处于等待状态,你执行notify_all,唤醒所有线程,但是只有一个线程获得资源,其他线程没有获得资源。
219-C++多线程(条件变量)相关推荐
- 5.3多线程条件变量
多线程条件变量应用例子 Input_manager.h InputOpr 结构体添加 进程 ID :pthread_t t_TreadID; int AllInputDevicesInit(void) ...
- linux多进程条件变量,Linux 多线程条件变量同步
条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作.条件变量使用的条件本身是需要使用互斥量进行保护的 ...
- Linux Qt使用POSIX多线程条件变量、互斥锁(量)
今天团建,但是文章也要写.酒要喝好,文要写美,方为我辈程序员的全才之路.嘎嘎 之前一直在看POSIX的多线程编程,上个周末结合自己的理解,写了一个基于Qt的用条件变量同步线程的例子.故此来和大家一起分 ...
- 多线程条件变量(pthread_cond_wait)用法
条件变量是利用线程间共享得全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起:另一个线程使"条件成立"给出条件成立信号.为了 ...
- 多线程---条件变量
互斥器和条件变量的区别:互斥器具有加锁原语,用来进行排他性的访问共享数据,而条件变量具有等待原语,用于等待某个事件的发生. 等待条件变量的正确姿势: void wait() {mutex.lock() ...
- linux 多线程条件变量,linux多线程之条件变量
假设有共享的资源sum,与之相关联的mutex 是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得 ...
- Java线程:新特征-条件变量(转)
条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量.但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义. 这里的条件和 ...
- Java线程详解(16)-条件变量
条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量.但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义. 这里的条件和 ...
- 【C++】多线程互斥锁、条件变量
我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!! 先来看下面这段简单的代码: int g_num = 0 ...
最新文章
- 计算机导论excel,[计算机导论实验三Excel.doc
- SQL 存储过程 解析XML
- pci总线定时协议_汽车总线测试的“解忧杂货店”
- Iaas,Paas,Saas三者的区别联系是什么?
- 头条终面:写个消息中间件
- Core Graphics 定制UIVIew 处理图片
- 解决android帮助文档打开慢
- java学习笔记------ PrintStream
- JS判断手机浏览器是横屏or竖屏
- Zookeeper集群部署及报错分析
- windows 实用小工具(截图、进程管理)
- 3dContactPointAnnotationTool开发日志(八)
- 计算机基础教程7 - 数字系统
- 问卷星指定内容批量自动填写的Python+Selenium+Pandas+Excel解决方案
- dnf丢失clientbase_登录dnf时出现dnfbase.dll的丢失
- python无法打开h5权限_求助:python post请求访问不到数据
- Dubbo 常见的负载均衡(Load Balance)算法,一起学习一下吧~
- 喜大普奔!rgee能用了!R语言也可以使用Google Earth Engine了!
- fw150rm刷openwrt固件_今晚把本版几乎所有固件刷了个遍。发现Padavan固件速度吊打OpenWrt,有人知道原因吗?...
- ONES 解码:为何数字化是“超级工程”
热门文章
- 计算机以太网依赖服务或无法启动,win7 网络和共享中心提示“依赖服务或组无法启动” 解决方法...
- 如何运用AI聊天机器人,助推内容营销活动取得成功?
- Windows下文件批量重命名bat脚本
- 小程序实现活动倒计时功能
- Spring框架设计模式分析
- 关于冷备份与热备份的对比理解
- 移动信号e经常无服务器,手机信号差,从4G突然变成“E”?真实原因是这样的!...
- 《深入理解计算机系统》 练习题3.49详解
- 书法 | 从零学硬笔,我的三天成长路 2
- 安徽省2018工程计价电子版_2018安徽省装饰工程计价定额.pdf