1、死锁的概念:
对于锁这个字,大家一定不会陌生,因为我们生活中就存在着大量的锁,它们各个方面发挥着它的作用,现在世
界中,就是阻止某些人做某些事,例如,门锁就是阻止除了屋主之外的人进入这个房子,你进入不到这个房子,也就
不能使用房子里面的东西。
死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现
象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进
程(线程)称为死锁进程(线程)。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程(线程)在
无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
一种交叉持锁死锁的情形,此时执行程序中两个或多个线程发生永久堵塞(等待),每个线程都在等待被其它线
程占用并堵塞了的资源。例如,如果线程 1 锁住了记录 A 并等待记录 B,而线程 2 锁住了记录 B 并等待记录 A,这
样两个线程就发生了死锁现象。在计算机系统中 , 如果系统的资源分配策略不当,更常见的可能是程序员写的程序有
错误等,则会导致进程因竞争资源不当而产生死锁的现象。简单的说一个进程持有了一个锁之后,在临界区内又去申
请该锁,它将不得不等待该锁被释放,但因为它本身在等待申请该锁,所以永远不会有机会释放锁并得到锁,最终结
果就是死锁。因为很多锁都不是可递归锁,所以不要尝试在一个线程内多次申请同一个锁。
原理:竞争资源,进程推进顺
2、死锁产生的原因:
1)系统资源不足;
2)进程(线程)推进的顺序不恰当;
3)资源分配不当。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入
死锁;其次,进程运行推进顺序与速度不同,也可能产生死锁。
3、死锁的形成场景:
1)忘记释放锁:在申请锁和释放锁之间直接return
2)单线程重复申请锁:一个线程,刚出临界区,又去申请资源。
3)多线程多锁申请:两个线程,两个锁,他们都已经申请了一个锁了,都想申请对方的锁
4)环形锁的申请:多个线程申请锁的顺序形成相互依赖的环形4、死锁的条件:
1)互斥条件:简单的说就是进程抢夺的资源必须是临界资源,一段时间内,该资源只能同时被一个进程所占有。
2)请求和保持条件:当一个进程持有了一个(或者更多)资源,申请另外的资源的时候发现申请的资源被其他进程所持有,当前进程阻塞,但不会是放自己所持有的资源。
- 3)不可抢占条件:进程已经获得的资源在未使用完毕的情况下不可被其他进程所抢占
在很多情况下这些条件都是合乎要求的。例如,为确保结果的一致性和数据库的完整性,互斥是非常有必要的。同理,不能随意的进行资源抢占。比如,当涉及数据资源时,必须提供回滚恢复机制(rollback recovery machanism)以支持抢占资源,这样才能把进程和他的资源恢复到以前适当的状态,使得进程最终可以重复他的动作。
前三个条件都是死锁存在的必要条件,但不是充分条件。对死锁的产生还需要第四个条件:
4)循环等待条件:存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源。
这里所说的资源不仅包括硬件资源或者其他的资源,还包括锁,锁也是一种资源,锁的争用也会导致死锁
5、预防死锁:
预防死锁的办法就是破坏死锁的四个必要条件,只要破坏了条件,死锁自然就不会产生了,简单的描述一下破坏四个
条件的思想
(1)破坏请求和保持条件:
1)所有进程在开始运行之前,必须一次性获得所有资源,如果无法获得完全,释放已经获得的资源,等待;
2)所有进程在开始运行之前,只获得初始运行所需要的资源,然后在运行过程中不断请求新的资源,同时释放自己
已经用完的资源。
相比第一种而言,第二种方式要更加节省资源,不会浪费(因为第一种可能出现一种资源只在进程结束用那么一小
下,但却从头到尾都被占用,使用效率极低),而且,减少了进程饥饿的情况。
(2)破坏不可抢占条件:
说起来简单,只要当一个进程申请一个资源,然而却申请不到的时候,必须释放已经申请到的所有资源。但是做
起来很复杂,需要付出很大的代价,加入该进程已经持有了类似打印机(或者其他的有必要连续工作的)这样的设
备,申请其他资源的时候失败了,必须释放打印机资源,但是人家用打印机已经用过一段时间了,此时释放打印机资
源很可能造成之后再次是用打印机时两次运行的信息不连续(得不到正确的结果)。
(3)破坏循环等待条件:
设立一个规则,让进程获取资源的时候按照一定的顺序依次申请,不能违背这个顺序的规则。必须按照顺序申请
和释放,想要申请后面的资源必须先把该资源之前的资源全部申请,想要申请前面的资源必须先把该资源之后的资源
(前提是已获得)全部释放。
(4)破坏互斥条件:
没法破坏,是资源本身的性质所引起的。
在死锁预防中通过约束资源请求,防止四个死锁条件中至少一个的发生。这可以通过防止发生三个必要策略条件中的一个(互斥、占有且等待、非抢占)间接完成,也可以通过防止循环等待直接完成,但这都会导致低效的资源使用和低效的进程执行。死锁避免则相反,它允许三个必要条件,但通过明智的选择,确保永远不会到达死锁点,因此死锁避免需要更多地并发。在死锁避免中,是否允许当前的资源分配请求是通过判断该请求是否可能导致死锁来决定的。因此,死锁避免需要知道将来的进程资源请求的情况。
(1)有序资源分配法:
这种算法资源按某种规则系统中的所有资源统一编号(例如打印机为1,、磁带机为2、磁盘为3、等等),申请时必须以上升的次序。
采用有序资源分配法:R1的编号为1,R2的编号为2,;PA:申请次序应是:R1,R2;PB:申请次序应是:R1,R2;这样就破坏了环路条件,避免了死锁的发生。
(2)银行家算法(最具有代表性)
该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的请求,就满足申请者的
请求。这样申请者就可以很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可
避免死锁的发生。
算法原理:把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资
源相当于用户向银行家贷款。为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里
得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
7、死锁的检测和解除:使用类似银行家算法的方式就可以简单的检测死锁
死锁解除:
1)终止进程(简单粗暴),就是字面上的,你们死锁了,我就把你们一起杀掉,缺点就是如果一个进程跑了很长时
间,但是被杀了,还得从头来。
2)逐个终止进程,按照某种顺序,挨个杀死进程,每杀一个进程就去看看死锁解除了没有(每杀一个进程都会释放
一些资源,如果释放好粗来的资源解决了死锁问题,就没必要再滥杀无辜了),没解除就继续杀。
第二种方式显然人性化了许多,但是按照某种顺序显得很朦胧,这里的某种顺序就是指死锁解除算法,有很多,
这里不再赘述。
注意:
写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死。假如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3,那么 所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难,则应该尽量使用pthread_mutex_trylock调用代替pthread_mutex_lock 调用以免死锁。