1. 前言
操作系统中的很多资源都是多个进程或者多个线程之间共享的,例如同一个文件,可能同时会被多个程序读写。或者是一个内存变量,存在同时被多个线程修改的可能。如果资源能够不能以合理的顺序访问就可能产生冲突,这种竞争资源的现象可能造成阻塞,引发死锁。
2. 死锁
面试官提问: 谈谈操作系统中的死锁是什么,产生死锁的条件是什么?有什么解决方案?
题目解析:
这是一种经典的提问方法,对于一个问题 A,面试官提出问题 A 的定义,候选人阐明定义之后,才能确保候选人和面试官彼此讨论的是同一个问题,而不是牛头不对马嘴。其次才是讨论这个问题的起源,在提出问题之后,当然需要给出解决方案,解决方案的优劣可以反应候选人对面试题目的理解程度。
2.1 死锁定义
死锁(DeadLock)可以发生在多个进程之间或者是多个线程之间,本文以线程作为观察对象。那么死锁的定义就是多个线程竞争同一个资源造成的僵局,如果没有外力推动,这种僵局会一直持续下去,线程的状态都无法继续推进。例如操作系统中有两个线程,分别是线程 A 和线程 B,线程 A 按照先获取锁 a 再获取锁 b 的顺序占用锁的资源,线程 B 按照先获取锁 b 再获取锁 a 的顺序占用锁。
线程 A 占用了锁 a,线程 B 占用了锁 b,线程 A 需要占用锁 b 之后才能释放锁 a,线程 B 则需要占用锁 a 之后才能释放锁 b,两个线程都进入了等待对方释放锁的状态,造成死锁。
2.2 死锁条件
造成进程或者线程死锁有四个必要条件:
(1)互斥条件:进程(线程)对于分配的资源有排他性,排他性是指一个资源在同一段时间内只能被一个进程(线程)占用。
(2)请求和保持条件:进程(线程)因为请求资源导致阻塞时,对于已经获得资源不会主动释放。通俗来说就是已有的资源不会放弃,没有的资源会持续请求。
(3)不可剥夺条件:进程(线程)在获得的资源没有使用完成之前,资源不能被剥夺,只能等进程(线程)主动释放。
(4)循环等待条件:所有等待的进程(线程)在发生死锁时,都会形成一个死循环环路,这也是造成死锁的直接原因。
2.3 死锁解决方案
死锁的解决方案就是从产生死锁的必要条件入手,如果不满足必要条件,那么就失去了发生死锁的可能性。还是以线程为例,给出死锁的解决方案:
(1)破坏请求条件:一次性给线程分配所有的资源,例如上述案例直接给线程 A 分配锁 a 和锁 b。
(2)破坏保持条件:只要存在任何一个资源不能被分配,已有被分配的资源也不能保持。例如上述案例中线程 A 不能获得锁 b,那么需要主动释放锁 a。
(3)破坏不可剥夺条件:只要存在任何一个资源不能被分配,已有被分配的资源可以被强制释放。
(4)破坏循环等待条件:所有的线程按照提前指定的顺序请求资源,释放资源的顺序刚好相反。
避免死锁的经典算法有银行家算法,我们把操作系统比喻成银行家,操作系统管理的资源就是银行中的资金,进程(线程)就是顾客,获取资源的过程就是向银行家索要贷款的过程,线程在获取资源前需要申明自己需要的每种资源的最大数量,操作系统计算在分配这些资源之后,是否会让系统处于不安全的状态。总结来看,银行家算法是一个动态判断死锁的算法。
3. 小结
本章节介绍了死锁的定义、产生原因以及典型的解决方案,与死锁对应的还有活锁的概念,活锁在尝试获取资源失败之后,会主动释放自己持有的锁,然后再重试整个拿锁流程。与活锁对应的是进程的饥饿,如果一个进程永远拿不到需要的资源,服务会一直阻塞,被比喻为饥饿。关于死锁,还会涉及到编程实战中如何避免死锁以及死锁的检测条件,候选人可以自行深入探讨。