多个进程在交替执行的时候,如果控制不好,就会出现死锁的情况;这个问题需要操作系统去处理。
死锁举例
如下例子:
Producer(item)
{
P(mutex);
P(empty);
// 代码
V(mutex);
V(full);
}
Consumer()
{
P(mutex);
P(full);
// 代码
V(mutex);
V(empty);
}
一种可能的调用顺序如下:
当mutex=1,empty=0时,首先Producer(),然后Consumer;
mutex=1,empty=0;首先调用Producer(),P(empty)的时候发现empty已经为0,进入睡眠。这时候无论调用多少个
Producer进程都会进入睡眠;如果调用Consumer()进程,因为mutex为1,前面调用producer()的时候已经P(mutex)了,在Consumer中再次P(mutex),那么Consumer进程会睡眠;这时候无论来多少个Consumer都会睡眠。
那么Producer什么才能wakeup呢?当V(mutex)时,但是V(mutex)在Consumer中,而且Consumer进程会在P(mutex)的时候就睡眠。也就是Consumer持有Producer所需要的资源不释放,导致Producer无法执行。换个角度,
为什么Consumer会不执行呢?也是因为Producer持有Consumer资源不释放导致的。
死锁的基本概念
死锁的定义:
死锁是指由于两个或者多个进程互相持有对方所需要的资源,导致这些进程处于等待状态,无法前往执行。
死锁会造成什么后果呢?
CPU利用率下降,也就是电脑会变卡。
想要知道某种情况的解决方法肯定首先得知道这种情况的产生原因。
死锁的成因
-
资源是互斥的,一旦占有别人无法使用。
比如某个Producer进程sleep了,无法V(mutex);其他的Producer进程不知绕过前面P(mutex)而直接执行V(mutex); -
进程占有了资源,不释放就再去申请资源。
一个Producer进程P(mutex)占有了mutex资源,但是没有V(mutex);其他的Producer进程仍然P(mutex),无法V(mutex)
死锁的四个必要条件
- 互斥使用
资源的固有特性 - 不可抢占
资源只能自愿放弃,无法抢占 - 请求和保持
首先占有资源,不释放,再去申请资源 - 循环等待
就像上面第一个例子,Producer和Consumer都持有对方的资源,但是都无法释放,构成一个循环。
Producer的P(mutex)如果要继续执行,需要等待Consumer的V(mutex);Consumer的V(mutex)如果想继续执行,
需要等待Consumer的P(mutex)继续执行;而Consumer如果想继续执行,那么就得等待Producer的V(mutex);而
Producer的V(mutex)想继续执行,就得等待Producer的P(mutex);构成一个环路。
死锁的处理方法概述
1,死锁预防
2,死锁避免
3,死锁检查+恢复
4,死锁忽略
下面具体谈谈这四种方式
死锁的处理方法
死锁预防
这里介绍两种方式,
方式一:一次性申请所有需要的资源。这样就不会占有资源再去申请其他资源了。破坏了必要条件3。
但是这种方式存在很大的不足:
第一点:在程序运行前就要知道这个程序会申请多少资源,需要预知未来,变成困难
第二点:可能许多资源分配后很长时间后才使用,资源利用率低
方式二:资源按顺序申请,这种方式不会造成环路等待。假设有十个资源,申请的时候必须按照顺序申请,需要资源1就申请资源1,需要资源2就申请资源2,但是如果需要使用资源10,就必须将前面的资源都申请了;但是也会造成很大的资源浪费。
从这里也能看到,死锁预防的方法并不好。
死锁避免
加果系统中的所有进程存在一个可完成的执行序列P1…Pn,则称系统处于安全状态。
那这个安全序列怎么找到呢?使用银行家算法
Allocation | Need | Available |
---|---|---|
ABC | ABC | ABC |
--------- | ------------ | ------------ |
PO 010 | 743 | 230 |
P1 | 302 | 020 |
P2 302 600 | ||
P3 | 211 010 | |
P4 | 002 431 | |
P0到P4表示五个进程,ABC表示三种资源,ABC下面的数字表示对应的资源个数,Allocation表示当前进程占用的资源,
Need表示该进程执行需要用的资源,Available表示系统剩余的资源。
银行家算法大意是:如果P1要执行,那么假设让P1执行一下,如果P1执行之后没有死锁,没问题;那就让P1执行。如果P1执行之后引起死锁了,那就不让P1执行。这就是死锁避免。
m 表示资源数量,n表示进程个数
int Available[1...m] ;//每种资源剩余数量
int Allocation [ 1...n, 1...m] ;//己分配资源数量
int Need [ 1..n, 1..ml;//进程还需的各种资源数量
int Work [ 1..m] ;//工作向量
bool Finish [ 1..n] ;//进程是否结束
Work=Available;Finish[1..n]=false;
while(true){
for (i=1;i<=n; i++)
{
if(Finish[i]==false && Need [i] <= Work[i] )
{
Work=Work+Allocation[i];
Finish[i]=true; break;
}
else
{
goto end;
}
)
)
End:
for (i=1;i<=n; i++)
if(Finish[i]==false)return "deadlock";
银行家算法的时间复杂度是O(mn^2);如果每次申请资源都调用一次,那么系统的开销太大了。
死锁检测+恢复
因为银行家算法的效率太低了,所以采取检测+恢复的方式。定时检测或者资源利用率低的时候检测。但是检测到某些进程死锁之后如何处理呢?回滚?回滚需要机制的支持;而且如果是一些文件修改的进程呢?所以也不好处理。
死锁忽略
碰见死锁直接忽略,这才是windows和Linux采用的机制;因为死锁本来就是小概率事件,而且重启可以解决这个问题,对于个人PC机来说,一般也不会造成很大影响。
参考资料
哈工大李志军操作系统