在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。那么到底怎样的情形才会产生死锁呢?
典型的两种死锁情形:
(一)线程自己将自己锁住
一般情况下,如果同一个线程先后两次调用lock,在第二次调⽤用时,由于锁已经被占用,该线程会挂起等待占用锁的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此 就永远处于挂起等待状态了,于是就形成了死锁(Deadlock)。
(二)多线程抢占锁资源被困
又如线程A获 得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放 锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都 永远处于挂起状态了,死锁再次形成。
二、计算机系统中的死锁
1、资源分类
(一)可重用性资源和消耗性资源
可重用资源:可供用户重复使用多次的资源。
性质:
1》每一个可重用性资源中的单元只能分配给一个进程(或线程)使用,不允许多个进程(或线程)共享。
2》可重用性资源的使用顺序:
请求资源—->使用资源—->释放资源
3》系统中每一类可重用性资源中的单元数目是相对固定的,进程(或线程)在运行期间既不能创建也不能删除它。
可消耗性资源:又称临时性资源,是由进程(或线程)在运行期间动态的创建和消耗的。
性质:
1》每一类可消耗性资源的单元数目在进程(或线程)运行期间是可以不断变化的,有时可能为0.
2》进程,或线程)在运行过程中可以不断的创建可消耗性资源的单元,将它们放入该资源类的缓冲区中,以增加该资源类的单元数目。
3》进程(或线程)在运行过程中可请求若干个可消耗性资源,用于进程(或线程)自己的消耗不再将它们返回给该资源类中。
可消耗性资源通常是由生产者进程(或线程)创建,由消费者进程(或线程)消耗。
(二)可抢占性资源和不可抢占性资源
可抢占性资源:☞某进程(或线程)在获得该类资源后,该资源可以被其他进程(或线程)或系统抢占。
CPU和主存均属于可抢占性资源。
不可抢占性资源:☞系统一旦把某资源分配该进程(或线程)之后,就不能强行收回,只能在进程(或线程)用完之后自行释放。
磁带机、打印机等都属于不可抢占性资源。
2、引起死锁的原因
(一)竞争不可抢占资源引起死锁
如:共享文件时引起死锁
系统中拥有两个进程P1和P2,它们都准备写两个文件F1和F2。而这两者都属于可重用和不可抢占性资源。如果进程P1在打开F1的同时,P2进程打开F2文件,当P1想打开F2时由于F2已结被占用而阻塞,当P2想打开1时由于F1已结被占用而阻塞,此时就会无线等待下去,形成死锁。
(二)竞争可消耗资源引起死锁
如:进程通信时引起死锁
系统中拥有三个进程P1、P2和P3,m1、m2、m3是3可消耗资源。进程P1一方面产生消息m1,将其发送给P2,另一方面要从P3接收消息m3。而进程P2一方面产生消息m2,将其发送给P3,另一方面要从P1接收消息m1。类似的,进程P3一方面产生消息m3,将其发送给P1,另一方面要从P2接收消息m2。
如果三个进程都先发送自己产生的消息后接收别人发来的消息,则可以顺利的运行下去不会产生死锁,但要是三个进程都先接收别人的消息而不产生消息则会永远等待下去,产生死锁。
(三)进程推进顺序不当引起死锁
上图中,如果按曲线1的顺序推进,两个进程可顺利完成;如果按曲线2的顺序推进,两个进程可顺利完成;如果按曲线3的顺序推进,两个进程可顺利完成;如果按曲线4的顺序推进,两个进程将进入不安全区D中,此时P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,如果继续向前推进,则可能产生死锁。
三、死锁的定义、必要条件和处理方法
1、死锁的定义:如果一组进程(或线程)中的每一个进程(或线程)都在等待仅由该组进程中的其他进程(或线程)才能引发的事件,那么该组进程(或线程)是死锁的(Deadlock)。
2、产生死锁的必要条件
(1)互斥条件。进程(线程)所申请的资源在一段时间内只能被一个进程(线程)锁占用。
(2)请求和保持条件。进程(线程)已经占有至少一个资源,但又提出了新的资源请求,而该资源却被其他进程(线程)占用。
(3)不可抢占条件(不可剥夺条件)。进程(线程)已获得的资源在未使用完之前不能被抢占。
(4)循环等待条件(环路等待条件)。在发生死锁时,必然存在一个进程(线程)—-资源的循环链。
3、处理死锁的方法
(1)预防死锁。破坏死锁产生的必要条件中的一个或多个。注意,互斥条件不能被破坏,否则会造成结果的不可再现性。
(2)避免死锁。在资源分匹配过程中,防止系统进入不安全区域。
(3)检测死锁。通过检测机构检测死锁的发生,然后采取适当措施解除死锁。
(4)解除死锁。在检测机构检测死锁发生后,采取适当措施解除死锁。
四、利用银行家算法避免死锁
1、银行家算法中的数据结构
(1)可利用资源向量Available[m]。m为系统中的资源种类数,如果向量Available[j] = K,则表示系统中Rj类资源由K个。
(2)最大需求矩阵Max[n][m]。m为系统中的资源种类数,n为系统中正在运行的进程(线程)数,如果Max[i][j] = K,则表示进程i需要Rj类资源的最大数目为K个。
(3)分配矩阵Allocation[n][m]。m为系统中的资源种类数,n为系统中正在运行的进程(线程)数,如果Allocation[i][j] = K,则表示进程i当前已分得Rj类资源的数目为K个。
(4)需求矩阵Need[n][m]。m为系统中的资源种类数,n为系统中正在运行的进程(线程)数,如果Need[i][j] = K,则表示进程i还需要Rj类资源K个。
以上三个矩阵间的关系:
Need[i][j] = Max[i][j] - Allocation[i][j]
2、银行家算法
设Request( i)是进程Pi的请求向量,如果Request(i) [j] = K,表示进程Pi需要K个Rj类型的资源。
(1)如果Request(i) [j] <= Need[i][j],转向步骤(2)。
(2)如果Request(i) [j] <= Available[j] ,转向步骤(3)。
(3)系统尝试着把资源分给进程Pi。
Available[j] = Available[j] - Request(i) [j];
Allocation[i][j] = Allocation[i][j] + Request(i) [j];
Need[i][j] = Need[i][j] - Request(i) [j];
(4)系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。
3、安全性算法
(1)设置两个向量:
1》工作向量Work[m],它表示系统可提供给进程继续运行所需要的各类资源数目,初始值Work = Available。
2》Finish:它表示系统是否有足够的资源分配给进程,使其运行完成。开始时Finish[i] = false,当有足够的资源分配给进程时Finish[i] = true。
(2)从进程(线程)集合中找到一个能满足下述条件的进程(线程)。
1》Finish[i] = false
2》Need[i][j] <= Work[j],如果找到转到步骤3》,没找到转到步骤4》。
3》Work[j] = Work[j] + Allocation[i][j] ;
Finish[i] = true;
go to step 2;
4》如果所有进程(线程)的Finish[i] = true都满足,表示系统处于安全状态,反之系统处于不安全状态。