1. 由一个示例引出死锁
多个进程在交替执行的时候,如果控制不好,就会出现死锁的情况;这个问题需要操作系统去处理。
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资源不释放导致的。
2. 死锁
2.1 定义
死锁是指由于两个或者多个进程互相持有对方所需要的资源,导致这些进程处于等待状态,无法前往执行。
2.2 产生的后果
CPU利用率下降,也就是电脑会变卡。
2.3 成因
1)资源是互斥的,一旦占有别人无法使用。
比如某个Producer进程sleep了,无法V(mutex);其他的Producer进程不能绕过前面P(mutex)而直接执行V(mutex);
2)进程占有了资源,不释放就再去申请资源。
一个Producer进程P(mutex)占有了mutex资源,但是没有V(mutex);其他的Producer进程仍然P(mutex),无法V(mutex);
2.4 四个必要条件
1)互斥使用
资源的固有特性
2)不可抢占
资源只能自愿放弃,无法抢占
3)请求和保持
首先占有资源,不释放,再去申请资源
4)循环等待
2.5 处理方法
2.5.1 死锁预防
这里介绍两种方式,
方式一:一次性申请所有需要的资源。这样就不会占有资源再去申请其他资源了。破坏了必要条件3。
但是这种方式存在很大的不足:
第一点:在程序运行前就要知道这个程序会申请多少资源,需要预知未来,变成困难
第二点:可能许多资源分配后很长时间后才使用,资源利用率低
方式二:资源按顺序申请,这种方式不会造成环路等待。假设有十个资源,申请的时候必须按照顺序申请,需要资源1就申请资源1,需要资源2就申请资源2,但是如果需要使用资源10,就必须将前面的资源都申请了;但是也会造成很大的资源浪费。
从这里也能看到,死锁预防的方法并不好。
2.5.2 死锁避免
如果系统中的所有进程存在一个可完成的执行序列P1…Pn,则称系统处于安全状态。那这个安全序列怎么找到呢?使用银行家算法
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);如果每次申请资源都调用一次,那么系统的开销太大了。
2.5.3 死锁检测+恢复
因为银行家算法的效率太低了,所以采取检测+恢复的方式。定时检测或者资源利用率低的时候检测。但是检测到某些进程死锁之后如何处理呢?回滚?回滚需要机制的支持;而且如果是一些文件修改的进程呢?所以也不好处理。
2.5.4 死锁忽略
碰见死锁直接忽略,这才是windows和Linux采用的机制;因为死锁本来就是小概率事件,而且重启可以解决这个问题,对于个人PC机来说,一般也不会造成很大影响。