死锁 + 条件变量 + 生产消费者模型

发布时间:2023年12月18日

死锁

现象 : 代码不会继续往后推进了

问题
一把锁有没有可能产生死锁呢?
有可能
在这里插入图片描述
线程第一次申请锁成功,继续再次申请,第二次申请就失败了,当前线程抱着锁就被挂起了
其他线程申请都失败阻塞了。
1.你抱着锁去休眠了
2.没有人释放锁了
3. 剩余线程申请锁都不成功
所有整个线程全部都卡住了

上面是单执行流,单锁,更一般的情况是多锁,多执行流

一组执行流(这里按多线程说),并发编程时锁可能不止一个,正常编程时各个线程都由调度器自由去申请锁,此时就可能出现问题,一个线程持有一把锁,另一个线程持有另一把锁,但是他们两个却互相申请对方的锁,进而导致一种永久等待的状态,这种状态成为死锁。

所有的死锁都是由于程序员代码写的不太合适或者调度过程中出现异常问题

例子
张三和李四去商店买糖,他们俩每人只有五毛钱,但是棒棒糖一块钱,张三要李四的那五毛,
李四要张三那五毛,他们两个人就在商店这里卡住了。

多线程会产生死锁其实有四个必要条件
什么叫必要条件?
只要产生了死锁必定满足这四个条件,并不代表使用这四个条件百分百产生死锁
反过来理解
只要四个条件中有一个条件不满足就一定不会产生死锁

1.互斥条件
要有死锁的前提肯定是要先用了锁
2.请求与保持条件
比如例子中张三请求李四的五毛钱并且不释放自己的五毛,李四也遵循相同的规则
3.不剥夺条件
张三拿着自己的五毛钱还在请求对方的五毛,但不能强行抢李四的五毛
4.循环等待条件
若干执行流之间形成一种头尾相接的循环等待资源的关系
在这里插入图片描述
我们写的抢票就是遵循互斥条件,有请求与保持,只不过只有一把锁,也有不剥夺条件,但是没产生死锁
因为必须形成环路等待才能产生死锁
在这里插入图片描述

如何解决死锁问题呢?

理念
一旦有死锁必定要同时满足四个必要条件,解决的话必然需要破坏4个必要条件之一

方法
1.代码死锁了,那我重写一下代码我不用锁了那就没有互斥了也没有死锁了

2.请求与不保持
张三问李四要五毛失败了,此时张三把五毛释放了,反正张三也走不到后面买不了棒棒糖
具体怎么操作呢?
之前pthread_mutex_lock 线程申请锁失败后不是立马出错返回而是把自己阻塞住
pthread_mutex_trylock 申请锁如果失败它会立即返回,是申请锁的非阻塞版本
它返回后把锁释放掉然后从新开始申请锁,这样就很容易破坏请求与保持条件
我们用的lock本来就是保持的,你不给我锁我就在这阻塞住,只不过只有一把锁

3.破坏不剥夺条件,那就是要剥夺,剥夺的本质不就是释放对方的锁吗
怎么剥夺呢?
直接释放锁
以前讲解锁的原理不是把以前交换的锁换回去,而是直接把锁置1 了

第一条破坏改动比较大,2,3是通过接口可以实现的

4.环路等待问题,申请资源形成一个环路
在这里插入图片描述

要破坏它通过编码来完成
张三要李四的锁2,李四要张三的锁1,为什么会这样?
谁让你申请锁时不按顺序申请而是交叉着申请,为什么不让两个线程同时申请锁1然后再同时申请锁2。
也就是说你要同时持有两把锁你必须得按顺序申请锁,也就破坏了环路等待问题

避免死锁

破坏死锁的四个必要条件

加锁顺序保持一致
两个线程都按顺序申请锁1锁2,不要倒着来,尽可能减少环路等待

避免锁未释放的场景
写代码锁要尽快释放

资源一次性分配
尽量把资源一次性给线程,不要让线程花了多次持有锁申请资源
一次给它,意味着加锁场景少一些,产生死锁场景就少了

同步

概念

同步问题是保证数据安全的情况下,让我们的线程访问资源具有一定的顺序

同步是为了解决互斥中线程竞争锁的能力强弱不同导致其他线程饥饿问题。
我们之前讲VIP自习室时定的两个原则
1.外面来的,必须排队
2.出来的人,不能立马重新申请锁,必须排到队列的尾部
这只是解决方案之一
在这里插入图片描述
互斥本身就能解决一类问题,只不过可能因为竞争不均衡产生饥饿问题
为了避免饥饿采用同步来解决
不要认为互斥本身是一种问题,要理解成一种解决方案,只不过解决方案有自己的使用场景
,如果互斥不满足我们可以引用同步。

1.快速提出解决方案 — 条件变量

原理

在这里插入图片描述
线程可以申请多个条件变量,凡是能申请多个的这种对象,都要对它做管理,不然我怎么知道去哪个条件变量下排队
则线程库一定要对多个条件变量做管理,怎么管理?先描述在组织
条件变量结构体中一定有一个等待队列是task_struct* wait_queue
还有一个铃铛,当一个线程访问临界资源完了,出来它要去队列中去排队,排队前要先敲一下铃铛,也就是铃铛响了唤醒队列中等待的线程。
其他线程来了如果资源不就绪,也要到队列里等待

线程在排队之前得先申请锁
条件变量必须依赖于锁的使用

接口

条件变量
定义全局,定义局部
和锁的接口差不多
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如果资源不就绪,让线程去等待队列里去等

在这里插入图片描述
signal 唤醒指定条件变量下等待的一个线程
boradcast 唤醒队列里所有的线程


快速看到多线程同步的效果 比如抢票

这里用多线程访问cnt
对cnt做加加,其实就和抢票差不多
我们没管临界资源的状态情况,也就是临界资源就不就绪的问题
在这里插入图片描述
为什么pthread_cond_wait在加锁后面?
1.pthread_cond_wait参数中需要一把锁,它会让线程去指定条件变量中排队的时候,会自动释放这把锁,锁一释放,其他线程也可以成功申请到锁再次进入条件变量中排队(我们是直接让线程去等待队列里排队,而没有看资源是否就绪)
所以等待队列中在这个时候一定存在大量的线程
我们今天是5个线程,所以5个线程直接去休眠了,也就不会去执行后面的动作了什么cout…

关键问题谁来让5个线程赶紧跑起来呢?
我们想让主线程进行控制唤醒
用signal唤醒在cond的等待队列中等待的一个线程,默认都是第一个
也就是说我们先让所以线程去等,然后后面一个个唤醒,因为你们是一个个被唤醒的,唤醒你执行完访问临界资源后立马释放锁,释放锁又重新申请锁,去条件变量中排队,就顺序循环起来了
在这里插入图片描述
现象
在这里插入图片描述
你说咋是4,0,1,2,3这顺序,咋不是0,1,2,3,4,我创建线程不是按照0,1,2,3,4创建的吗?
这完全是由调度器决定

用usleep让创建线程时间稍微错开点,就可以按顺序创建一批线程,
在这里插入图片描述
这样现象更明显了,我现在能控制多线程访问临界资源的顺序了,不就是同步了吗
在这里插入图片描述
我们还可以用broadcast一次唤醒所有等待线程
在这里插入图片描述
在这里插入图片描述

这几个线程从队列里被唤醒后执行后续代码,它也是安全的,但是目前先不谈
在这里插入图片描述
还有一个问题
你pthread_cond_wait去队列里等待了,自动释放锁了,那你唤醒后呢?锁怎么处理的?
目前先不谈

问题
我们怎么知道我们要让一个线程去休眠了呢?你凭什么让一个线程休眠?
一定是临界资源不就绪,没错,临界资源也是有状态的!!

比如我们现在想让一个线程去休眠,你凭什么让我休眠啊,今天我们大家都无脑去休眠了,
可是未来不能这样啊,一定是临界资源不就绪此时才让线程去休眠的。

比如从一个队列里取数据入数据队列满了空了访问条件不就绪时才让线程去休眠

问题是你怎么知道临界资源是就绪还是不就绪的?
你自己判断出来的!
判断是访问临界资源吗?
必须是的,也就是判断必须在加锁之后!!!
未来对于临界资源的访问,你必须先加锁然后再判断然后才能休眠
注定了等待的过程在加锁和解锁之间

2. CP问题 — 理论

3. 快速实现CP

文章来源:https://blog.csdn.net/weixin_50809457/article/details/135031415
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。