目录
一、死锁的两种场景
第一种场景
- 线程加锁之后并没有将锁释放
- 1.这里我们模拟两个线程导致死锁的情况:将锁锁上之后线程退出,导致其他线程拿不到这把锁造成死锁情况
这时候我们来通过pstack来查看下调用堆栈
我们可以看到创建出来的两个线程只剩下了一个,并且他在死等锁
2.接下来我们来模拟一个线程将锁锁上没有释放,再次去申请锁,导致死锁的情况:
我们再来看看调用堆栈,可以发现和上面一样,同样是在死等锁
第二种场景
吃着碗里的,看着锅里的
这两个线程A和B都阻塞在自己的加锁逻辑当中去了
我们来写代码模拟一下这个过程,创建一个线程A和线程B
这时候查看调用堆栈,发现两个线程都在等待着锁,导致卡死了
二、死锁的gdb分析
先使用gdb attach 【pid】命令进入gdb调试状态
这时候再使用 thread apply all bt来查看所有线程的调用堆栈
我们这时候看到的是所有的线程,那么我们怎么看到某个具体的线程的调用堆栈呢?
这时候我们可以使用
t+线程序号 = 跳转到某个线程当中
我们如果想要进入某个具体的堆栈该怎么办呢?
f+【号】
我们还可以通过 p + 【变量名】来打印这个变量
其中__owner表示锁被谁拿走了
三、造成死锁的必要条件
-
3.1、不可剥夺
- 不可剥夺:线程获取到互斥锁之后, 除了自己释放,其他线程是不能进行释放的。
- 只能自己释放,不能其他线程代劳
-
3.2、循环等待
- 循环等待:线程A拿着1锁, 请求2锁, 同时线程B拿着2锁, 请求1锁。
-
3.3、互斥条件
- 互斥条件: 一个互斥锁,在同一时间只 能被一个线程所拥有
-
3.4、请求与保持
- 吃着碗里的, 看着锅里的。(已经拿到一个锁,还想请求另外一个锁,和上面的循环等待场景相似)
4个必要条件中,不可剥夺和互斥条件是互斥锁的基础属性,程序猿是没有办法通过代码改变的,
程序猿可以通过代码的手段破坏循环等待或者请求与保持
四、预防死锁
4.1.破坏必要条件:循环等得请求与保持
我们在加锁的时候可以用pthread_mutex_trylock()和pthread_mutex_timedlock( )函数,这样就能避免一直死等锁
4.2 加锁顺序一致,都先加1锁, 再加2锁
-
4.2、避免锁没有被释放
- 在所有可能线程退出的地方都进行解锁
-
4.3、资源一次性分配
- 多个资源在代码当中又可能每一个资源都需要使用不同的锁进行保护
- 例如:
- 全局变量A,需要1锁
- 全局变量B,需 要2锁
这就有可能多个线程在使用这两个资源的时候,出现循环等待的情况。
- 这里的解决方式我们只需要给将A和B用同一把锁保护即可(资源一次性分配)。