假设在一座狭窄的单行桥中央,两辆分别从两端驶入的汽车迎面相遇。如果两位司机都等着对方先退让而一动不动,那么两辆车永远也过不了桥。很难说是谁的错,但一个谁都无法前进的局面就这样形成了。
计算机内部也会发生一模一样的事情,这被称为“死锁(Deadlock)”。简单来说,死锁是指多个程序各自等待对方占有的资源,结果谁都无法继续工作而停摆的状态。计算机明明没有坏,却突然像卡住了一样,这类现象中有相当一部分正是死锁造成的。
本文将依次介绍死锁会以什么样子出现、产生死锁的四个条件是什么、用来说明死锁的著名案例,以及计算机应对死锁的方法。
互相等待而停摆的计算机
死锁是怎样产生的

在计算机内部,多个程序同时运行,共同使用打印机、文件、内存等资源。
(1) 一次只能一个使用的资源
有些资源如果被多个程序同时使用,内容就会混乱,因此一个程序在使用时,其他程序必须等待。这种让程序暂时独占资源的机制称为锁(Lock)。
(2) 顺序交错造成的停摆
例如,第一个程序锁住文件A并等待文件B,第二个程序锁住文件B并等待文件A。由于两个程序都不放开自己手中的资源,只等待对方的资源,结果两者都会永远停在那里。
(3) 表面上的样子
陷入死锁的程序连错误信息都没有,只是没有响应。在银行计算机网络、预订系统等需要同时处理大量请求的地方,这种停摆可能引发严重的问题。
产生死锁的四个条件

1971年,计算机科学家爱德华·科夫曼(Edward G. Coffman Jr.)总结指出,要产生死锁,必须同时具备以下四个条件。人们以他的名字将其称为科夫曼条件。
- 互斥(Mutual Exclusion):资源一次只能由一个程序使用。
- 占有并等待(Hold and Wait):程序已经占有一个资源,同时还在等待其他资源。
- 不可抢占(No Preemption):不能强行夺走别人占有的资源,只能由占有者自己释放。
- 循环等待(Circular Wait):1号等待2号,2号等待3号,3号又等待1号,等待关系像环一样首尾相连。
(1) 只要打断一个就能解开
四个条件中只要缺少一个,死锁就不会产生。因此,防止死锁的方法大多从打断这四个环节中的某一个开始。

哲学家就餐问题

说明死锁时最常用的故事,是源自荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)所提出问题的“哲学家就餐问题(Dining Philosophers Problem)”。
(1) 问题的情境
五位哲学家围坐在圆桌旁,每两人之间各放一把叉子,共五把。哲学家思考一阵后饿了,必须拿起左右两边的两把叉子才能用餐。
(2) 所有人都挨饿的时刻
如果五个人同时拿起左边的叉子,那么所有人都会等待右边的叉子。由于谁也不放下左边的叉子,最终五个人都吃不上饭,只能一直等下去。这是四个条件全部具备的典型死锁。
(3) 简单的解决办法
如果给每把叉子编号,并规定每个人都先拿编号较小的叉子,等待的环就不会形成。这很好地说明了,只需规定一个顺序,就能打破循环等待。
计算机应对死锁的方法

操作系统和数据库会根据情况选用多种方法。
(1) 预防
事先规定使用资源的顺序,或者让程序在一开始就一次性申请所需的全部资源,使四个条件中的某一个根本无法成立。
(2) 避免
在分配资源之前,先计算满足这个请求是否可能在之后导致死锁。戴克斯特拉设计的银行家算法(Banker’s Algorithm)是代表性的方法,类似于银行在发放贷款时,先衡量自己能否把所有客户的需求一直满足到最后。
(3) 检测并解除
先允许死锁发生,再查看谁在等待谁,一旦发现环,就终止某个程序或让它从头重新运行,以解开这个结。许多数据库都采用这种方式。
(4) 视而不见
如果死锁极少发生且损失很小,也可以选择不刻意防范,发生时重新启动即可。人们把它比作把头埋进沙子里的鸵鸟,称之为鸵鸟算法(Ostrich Algorithm)。
死锁并不是因为某一方犯了错,而是在所有人都攥着自己的那一份、只等待对方时产生的问题。所以,解决办法也要从规定顺序、有时先放下自己手中的东西开始。
明白只要松开互相紧握的手中的一只,路就会打开——这正是让停摆的系统重新运转起来的智慧的第一步。