死锁🔒的成因和解决方案📑
锁是操作系统和程序语言中常用的同步机制,用于保证共享资源的互斥访问,防止多个线程或进程同时访问共享资源,导致数据不一致或程序崩溃。
死锁是我们在进行并发编程中需要时刻避免的一个问题。当多个线程互相等待的时候对方释放资源,系统就会出现死锁。具体来说,当一个线程获得一把锁,但同时它需要获取另外一把锁才能够继续往下执行,而另外一把锁又被其他线程占用了,这时候就会产生死锁。
形成死锁的四个必要条件:
- 互斥使用
当一个线程拿到一把锁之后,另一个线程会一直陷入等待状态,无法正常执行程序。(锁的基本特点)
不可抢占
一个线程拿到锁,只能由自己自动释放,不能被其他线程进行抢夺。(不能进行强行夺取)
请求和保持
在占用一把锁后,还对其他线程中的锁进行请求,而且还一直保持着占用锁的使用。
循环等待
一个锁被占有,需要另一把锁进行解锁,悲观另一把锁已经被其他锁占有了,陷入了永远等待。
要满足4个条件才会形成死锁,在进行编程时,需要格外注意不能形成死锁。而避免死锁只需要去除其中一个条件就可以解开死锁。
解决死锁
在学习锁时,不可避免就会出现一个“吃拉面问题”。已知哲学家吃面是一个死锁问题。
只有拿到两根筷子才可以吃面,当只有五根筷子五个哲学家,一人一根筷子,而且只等待别人吃完面放下筷子时,自己才可以吃到面。这样的吃面方法就会导致死锁!
使用为锁(筷子)进行编号进行解开死锁。
约定:获取筷子只能从小到大编号进行获取
- A哲学家获取了1号筷子就会制约E哲学家获取筷子。相当于E哲学家没有筷子。
- 那么D玩家就可以拿到3号筷子和5号筷子成功吃到拉面。也就是避开死锁。
2. 预防死锁
在进行并发编程时,要避免使用多个锁,如果实在无法避免使用多个锁那就采用相同的获取锁顺序避免最简单的死锁情况。就意味着当一个线程在加锁时要使用相对约定好的顺序来获取锁,以此来避免死锁。
3. 避免死锁
在采用多个锁的情况下,采用预防算法来避免死锁,使得死锁无法成功,例如:“银行家算法”,这个算法就是利用预判的思想来观察下一步的操作会不会使得这个线程变成死锁状态,如果会死锁,则立马会停止操作,起到避免死锁的作用。
4. 检测死锁
在程序之中,可以编写代码来检测代码中是否存在死锁,并及时终止这些无法恢复的死锁进程,不过这个有一个缺陷,你不能一直循环找死锁,会浪费太多CPU资源,而是设置一个时间去检查程序中的死锁状态,所以有时候在程序中出现死锁时,无法及时终止死锁进程会浪费时间。
5. 解除死锁
当发生死锁时,尝试解除一部分资源,或采用抢占资源的方式强行中断一个进程以解除死锁,这个是解除了死锁条件中非抢占式来解除死锁的方式。
解除死锁,是一件非常复杂的事,需要综合考虑程序的性能效率和安全性,然后选择合适的方案,尽量挑选减少死锁发生和解除死锁高效的方法。