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,唤醒所有线程,但是只有一个线程获得资源,其他线程没有获得资源。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,你想了解关于 C++ 条件变量多线程中的使用。 在多线程中,条件变量通常用于线程间的同步,它可以让一个线程等待另一个线程的特定事件发生。当这个特定事件发生时,另一个线程可以通过条件变量来通知等待的线程,让它继续执行。 C++ 中的条件变量一般需要和互斥锁一起使用,以保证线程安全。当一个线程等待条件变量时,它会释放互斥锁,让其他线程可以继续执行,等到条件变量被唤醒后,线程会重新获得互斥锁并继续执行。 下面是一个简单的示例代码: ```cpp #include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cv; bool ready = false; void worker_thread() { // do some work { std::lock_guard<std::mutex> lk(mtx); ready = true; } cv.notify_one(); // 通知等待的线程 } int main() { std::thread worker(worker_thread); // 等待条件变量 { std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, []{ return ready; }); // 等待条件变量被唤醒 } std::cout << "Worker thread finished\n"; worker.join(); return 0; } ``` 在这个示例中,我们新开了一个线程 `worker_thread`,它会在一段时间后设置条件变量 `ready` 为 `true`,然后通知等待的线程。在 `main` 函数中,我们首先获得了互斥锁 `mtx`,然后调用 `cv.wait` 等待条件变量被唤醒。在等待期间,线程会释放互斥锁,让 `worker_thread` 可以继续执行。等到 `worker_thread` 设置了条件变量并通知后,`main` 函数会重新获得互斥锁并继续执行。最后,我们等待 `worker_thread` 结束并回收资源。 希望这个示例可以帮助你理解条件变量多线程中的使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值