死锁(Deadlock)是指多个进程因竞争资源而造成的一种互相等待对方手里的资源的僵局,其使得各个进程都被阻塞,即若无外力干涉,这些进程都无法向前推进。出现死锁时,系统必然同时满足互斥条件、请求和保持条件、不可抢占条件和循环等待这四个条件。一旦死锁发生,整个系统将停滞或
做空操作,操作员难以察觉,从而造成资源浪费。
1965年,
荷兰计算机科学家
狄克斯特拉(Edsger Wybe Dijkstra)在研究银行家算法的过程中,首次提出了死锁问题,以探索银行家如何安全地将有限资金借给多个客户。这一问题随后引起了广泛关注,并被众多科学家进一步研究。1971年,考夫曼(E. G. Coffman)等人在分析了大量死锁现象后总结了产生死锁的4个必要条件,并给出了有关多个资源实例的死锁检测算法。1973年,霍华德(Howard)提出了死锁综合处理的策略,即把系统中的资源分为几大类,整体上采用资源顺序分配法,再根据每类资源的特点选择最适合的方法。20世纪80年代以后,各种死锁检测、死锁解除、死锁避免等算法得到了广泛的应用和研究,涌现了大量的相关研究成果。
并行进程的执行改善了系统资源的利用率,提高了系统的处理能力,但同时也可能产生由于进程资源竞争或推进顺序不当,使系统进入死锁状态的问题。随着云计算和分布式系统的发展,死锁问题日益普遍,有效处理和预防死锁是确保系统效率和可靠性的核心挑战。
发展历程
1965年,
荷兰计算机科学家E.W.Dijkstra在研究
银行家算法时提出了死锁问题,并提出了单个资源类型的死锁避免的银行家算法。银行家算法(Banker's Algorithm)是Dijkstra为T.H.E操作系统设计的一种避免发生死锁的算法,该算法通过预测未来可能的资源请求,即在死锁形成之前进行评估,从而有效避免产生死锁。Banker's Algorithm需要检查申请者对各类资源的最大需求量,如果系统现存的各类资源可以满足它对各类资源的最大需求量,就满足当前的申请。
1971年,美国科学家Coffman等人在"System Deadlocks"中总结了产生死锁的4个必要条件:互斥条件、请求和保持条件、不可抢占条件、循环等待条件。Coffman在该文章中还给出了有关多个资源实例的死锁检测算法,该算法使用了一些随时间变化的
数据结构,且其检测算法为需要完成的所有进程探究各种可能的分配序列。
1972年,
加拿大科学家霍尔特(R. C. Holt)在其论文 “Some Deadlock Properties of
计算机 Systems"中第一次将死锁问题用分配图模型来形式化,并讨论了饥饿问题。饥饿和死锁一样,是一种由于资源竞争所导致的进程无法向前推进的现象,但饿死现象的进程不仅可能处于等待态,也可能处于运行态或就绪态,且进程等待的是会被释放但不会分配给自己的资源。
20世纪80年代以后,随着计算机系统规模的扩大和复杂度的增加,死锁问题逐渐成为实际系统中需要解决的重要问题。各种死锁检测、死锁解除、死锁避免等算法得到了广泛的应用和研究,涌现了大量的相关研究成果。例如,1987年,美国科学家巴赫(M. J. Bach)讨论了传统
unix内核如何处理死锁。1998年,
加利福尼亚大学卡勒(D. E. Culler)等教授讨论了网络中死锁问题的解决方案,其综合考虑资源管理、协议设计、算法优化等多个方面来避免死锁的发生,以确保网络的稳定性和可靠性。2003年,费尔利·迪金森大学教授莱文(G. N. Levine)进一步给出了有关死锁处理方面的研究,其对死锁的不同状态进行了更精确的分类和定义。
进入21世纪以后,随着
云计算、
大数据、物联网等新技术和新应用场景的兴起,死锁问题在
分布式系统、
并行计算、
嵌入式系统等领域变得更加复杂和重要。随着进程的数量增多,程序的
多线程化,持久文件和数据库服务器更受重视,死锁问题越来越普遍。新的死锁检测算法、预防机制和
故障容许度机制在不断涌现,以应对不断变化的系统环境和需求。
死锁的产生原因
系统资源的竞争
在系统中,某些非抢占式资源(例如磁带机和打印机)的数量有限,无法同时满足多个进程的需求。这种情况下,进程在运行过程中可能会因为争夺这些有限资源而陷入死锁。死锁通常发生在对非抢占式资源的竞争中,而抢占式资源(如CPU和主存)的竞争则不会导致死锁。
进程推进顺序非法
请求和释放资源的顺序不当,也同样会导致死锁。例如,进程分别保持了资源,而 申请资源、申请资源时,两者都会因为所需资源被占用而阻塞,于是导致死锁。
产生死锁的必要条件
产生死锁必须同时满足包括互斥条件、请求和保持条件、不可抢占条件和循环等待条件在内的4个条件,只要其中任一条件不成立,死锁就不会发生。
死锁的预防
预防死锁是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个以避免产生死锁。在产生死锁的四个必要条件中,由于互斥条件是非共享设备所必须的,不仅不能改变,还应加以保证,因此死锁的预防主要是通过破坏请求和保持条件、不可抢占条件以及循环等待条件来实现。
破坏“请求和保持”条件
系统通过保证当一个进程在请求资源时,该进程不能持有不可抢占资源来破坏“请求和保持”条件。该保证可通过以下两种协议实现。
第一种协议
协议规定进程在开始前一次性申请所有必需资源。如果系统不能满足全部资源需求,则不分配任何资源,直至所有资源可用。这避免了“请求”和“保持”死锁条件,简单、易行且安全,但可能导致资源浪费和进程饥饿。
第二种协议
改进版协议允许进程获取初期资源后开始运行,随后根据需要逐步释放已用资源并请求新资源。例如,一个处理任务的进程先复制数据,完成后释放资源再请求下一步所需资源。这种方式使进程更快完成任务,提升资源效率,减少了饥饿风险。
破坏“不可抢占”条件
协议规定,当持有特定不可被抢占资源的进程发出新资源请求而未获满足时,必须释放其当前保有的所有资源。后续再根据需要重新申请资源,以此方法破坏“不可抢占”条件。该方法实现起来比较复杂,且可能会造成进程前一阶段工作的失效。同时,还可能使进程的执行因为反复地申请和释放资源致而被无限地推迟,从而延长了进程的周转时间,增加了系统开销,降低了系统吞吐量。
破坏“循环等待”条件
通过对所有资源类型进行排序并赋予唯一序号,可以破坏“循环等待”条件。设定一个资源序列,每种资源赋予一个序号。例如,
盒式录音磁带驱动器、
HDD和打印机分别赋予不同的序号,函数按如下形式来定义:
协议规定进程按照资源的序号递增顺序请求资源。进程初始可以请求任意资源Ri,后续仅在条件下,才能请求资源。若需多个相同资源,需一次性请求。例如,进程若需打印机和磁带机,需先请求序号较低的磁带机。该策略避免资源分配图中形成环路,破坏“循环等待”条件。
死锁的避免
通过在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而避免发生死锁。
系统安全状态
在死锁避免方法中,把系统的状态分为安全状态和不安全状态。当系统处于安全状态时,可避免发生死锁。反之,系统可能进入死锁状态。在该方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,才可将资源分配给进程,否则,令进程等待。
系统安全状态是指系统能按某种进程推进顺序为每个进程分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成,其中被称为安全序列。如果系统无法找到这样一个安全序列,则称系统处于不安全状态。
例如,系统中有三个进程,以及12台
磁带机。各进程对磁带机的
总需求和在时刻已获得资源如下表所示,
此时存在一个安全序列,使得系统按此进程序列分配资源,可保证每个进程都顺利完成。例如先为进程分配给两台磁带机,使之继续运行,其完成便可释放出4台磁带机,于是剩余可用磁带机增值5台;再将5台磁带机全部分配给进程,待运行完成后,释放磁带机,是剩余可用磁带机增值10台;之后进程可获得足够的资源以运行,从而使三个进程都能顺利完成。因此,在时刻系统是安全的。
银行家算法
该算法要求每一个新进程在进入系统时,必须申明其在运行过程中可能需要的每种资源类型的最大单元数目,其数目不应超过系统所拥有的资源总量。而当进程请求一组资源时,系统需先确定是否有足够的资源分配给该进程,并进一步计算若将这些资源分配给该进程,是否会使系统处于不安全状态。若系统有足够的资源分配,并不会导致系统进入不安全状态,则将资源分配给该进程,否则让进程等待。
为实现银行家算法,系统中需设置四个
数据结构,分别用来描述系统中可利用的资源、所有进程对资源的最大需求、系统中的资源分配,以及所有进程还需要多少资源。
三个矩阵Max、Allocation、Need之间存在关系:
死锁检测和解除
当系统中不采取死锁预防措施和死锁避免算法时,系统很可能会发生死锁。此时,系统应提供死锁检测算法用于检测系统状态,以确定系统中是否发生了死锁;以及死锁解除算法用于认定系统中已发生了死锁时,将系统从死锁状态中解脱出来。
死锁的检测
死锁检测要求系统中必须保存有关资源的请求和分配信息,以及提供一种算法利用这些信息来检测系统是否已进入死锁状态。
资源分配图
资源分配图是由一组结点和一组边所组成的一个
对联 ,可以更精确地描述死锁,定义和限制如下:
例,在下面的资源分配图中,用圆形表示进程,用矩形表示资源类型。由于可能有多个实例,矩形内的点的数量表示资源类型的实例数量。
根据资源分配图的定义,证明了如果分配图没有环,那么系统就没有进程死锁。
死锁定理
简化资源分配图可检测系统状态S是否为死锁状态。以下面所示的资源分配图为例,简化方法如下:
为死锁的条件是当且仅当状态的资源分配图是不可完全简化的,该条件为死锁
定理。
死锁的解除
一旦检测出死锁,就应立即采取相应的措施来解除死锁。死锁解除的主要方法有资源剥夺法和撤销进程法。
死锁的处理策略
处理死锁的策略有预防死锁、避免死锁、死锁的检测,各有优缺点如下:
死锁综合策略
上述策略中,无论哪种方法都无法适用于各类资源。1973年,Howard对此提出了死锁综合处理的策略。其具体步骤为:首先根据资源特性把资源分成几大类;为了预防资源类之间由于循环等待产生死锁,整体上采用资源线性排序策略;最后对每类资源根据其特点择最适合的方法。
死锁与饥饿
在一个动态系统中,进程会不断地请求资源和释放资源,并发执行向前推进。进程申请资源后,申请的资源正在被其他进程使用,则需要等待,当等待时间给进程的推进和响应带来明显的影响时,就称发生进程的饥饿。当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时,称该进程被饿死。
死锁和饥饿的共同点是都是由于资源竞争所导致的进程无法向前推进的现象,但从进程状态考虑,参与死锁的进程都处于等待态,而发生饿死现象的进程不仅可能处于等待态,也可能处于运行态或就绪态;另外死锁进程等待的是永远不会被释放的资源,而饿死进程等待的是会被释放但不会分配给自己的资源。
死锁处理实例
Windows
在 Windows 操作系统中,针对死锁问题,有以下具体策略:
Linux
在 Linux 操作系统中,针对死锁问题,有以下具体策略:
相关概念
可抢占资源与不可抢占资源
计算机系统中的资源按照占用方式,可分为可抢占资源与不可抢占资源。
可重用资源和可消耗资源
计算机系统中的资源按照资源的利用方式,可分为可重用资源和可消耗资源。
①每一个可重用资源中的单元只能分配给一个进程使用,不允许多个进程共享。
②进程在使用资源时需要遵循的使用顺序为:申请资源,若进程申请资源失败,则该进程被阻塞或循环等待;使用资源,即该进程对资源进行操作;释放资源,该进程使用完资源后自己主动释放资源。
③系统中每一类可重用资源中的单元数目是相对固定的,进程在运行期间既不能创建也不能删除它。
①每一类可消耗资源的单元数目在进程运行期间是可以不断变化的。
②进程在运行过程中可以不断地创建可消耗资源的单元,将它们放入该资源类的
缓冲区中以增加该资源类的单元数目。
③进程在运行过程中可请求若干个可消耗资源,用于进程自己的消耗,不再将它们返回给该资源类中。
未来发展方向
分布式系统中的死锁检测
分布式数据库系统在提供强大计算和存储能力的同时,由于在各种事务处理过程中大多采用封闭技术,以及大量用户频繁访问分布式数据库,容易发生同资源异操作的问题,从而导致分布式数据库死锁。 若计算机系统无法及时检测到死锁,死锁持续时间不断延长,最终会导致系统
瘫痪。 目前,已有多种针对分布式系统的死锁检测算法被提出。如闫盛楠教授于2020年提出的提出一种基于关联规则挖掘的检测方法,但是该方法受到空间爆炸影响不能及时更新分布式数据库死锁集合,导致死锁检测精准度较低。基于静态分析的死锁检测方法的提出有效提高死锁检测的精准度,但其在理论和工程上的难点方面还有很多有待研究和改进的地方,如完善分布式数据库敏感数据分析流程,以提高检测效率。
新型动态死锁分析方法
常见的死锁检测方法有模型检测、
符号执行、静态分析和动态分析四种。其中,动态分析通过分析程序运行轨迹及研究锁授权顺序中存在的特定模式,从而进行死锁检测,其具有分析效率高、可自动化进行的优点,但同时由于对锁的授权/释放操作及其执行场景难以准确刻画,可能导致误报现象。因此,如何减少误报,提高准确率,对于死锁检测具有重要意义。
Linux操作系统用户态死锁检测方法
Linux系统提供了基于Lockdep的内核死锁检测方法,可以有效检测内核死锁问题。目前Lockdep已经移植到用户态,即liblockdep,但是仍然缺乏对信号量和
自旋锁的死锁检测。为此,
东南大学陈教授提出了一种Linux用户态死锁检测方法,其基于统一的实现机制,有效解决了Linux操作系统在运行时由于用户态
互斥锁、信号量、读写锁、自旋锁等资源争用而导致的
多线程死锁检测问题,具有很好的通用性。