操作系统——死锁整理

死锁

一.死锁的概念

死锁是指多个进程在运行过程中因资源争夺而造成的一种僵持状态。在这种状态下,这些进程都无法继续向前执行。

再来看看下面这个例子:

在这里插入图片描述

假设现有一台打印机和一台扫描仪,现在有两个进程分别进行左右的一系列操作,当它们分别执行了1和2两个操作获取了打印机和扫描仪两个临界资源后,两个进程分别要执行3和4操作申请扫描仪和打印机资源,但都申请不到而都被阻塞,这样两个进程一直处于这种僵持状态,下面的操作都无法继续执行,这就是死锁现象。

二.死锁产生的原因

产生死锁的原因可以总结为如下两点:

1.竞争资源满足不了各个进程的需要。比如如果上面的例子中将打印机和扫描仪都增加到两台,就不会出现死锁现象。

2.进程运行时请求和释放资源的顺序不当。如果将上面例子中P2的2和4两个操作颠倒过来,也不会出现死锁。

三.经典问题

生产者消费者,读写者,哲学家就餐等多线程问题均有可能引起死锁,下面就一个Linux下C语言编写的哲学家就餐问题进行分析。

对于不熟悉linux多线程编程的同学,下面有几个点在注释中进行了说明:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

sem_t chopstick[6] ;	//为了从编号1开始,这里申请了6个信号量,也就是临界资源
//每个线程(哲学家)都会调用下面这个函数,每个哲学家会尝试拿起自己左右两边的筷子(申请chopstick[left]和chopstick[right]两个临界资源),如果同时拿到就进行就餐,就餐完毕释放两个资源。
void *eat_think(void *arg)
{
	char phi = *(char *)arg;
	int left,right;
	switch (phi){
		case 'A':
			left = 5;
			right = 1;
			break;
		case 'B':
			left = 1;
			right = 2;
			break;
		case 'C':
			left = 2;
			right = 3;
			break;
		case 'D':
			left = 3;
			right = 4;
			break;
		case 'E':
			left = 4;
			right = 5;
			break;
	}

	int i;
	for(;;){
		sleep(1);
        //通过sem_wait函数申请临界资源,相当于P操作
		sem_wait(&chopstick[left]);     //拿起左边的筷子
		printf("Philosopher %c fetches chopstick %d\n", phi, left);
		sem_wait(&chopstick[right]);	//拿起右边的筷子
		printf("Philosopher %c fetches chopstick %d\n", phi, right);

		printf("Philosopher %c is eating.\n",phi);	//就餐
		sleep(1);

        //sem_post函数释放临界资源
		sem_post(&chopstick[left]);
		printf("Philosopher %c release chopstick %d\n", phi, left);
		sem_post(&chopstick[right]);
		printf("Philosopher %c release chopstick %d\n", phi, right);
	}
}
int main(){
	pthread_t A,B,C,D,E;

	int i;
	for (i = 1; i <= 5; i++)
		sem_init(&chopstick[i],0,1);
	pthread_create(&A,NULL, eat_think, "A");
	pthread_create(&B,NULL, eat_think, "B");
	pthread_create(&C,NULL, eat_think, "C");
	pthread_create(&D,NULL, eat_think, "D");
	pthread_create(&E,NULL, eat_think, "E");

	pthread_join(A,NULL);
	pthread_join(B,NULL);
	pthread_join(C,NULL);
	pthread_join(D,NULL);
	pthread_join(E,NULL);
	return 0;
}

测试结果:

在这里插入图片描述

可以看到在光标处,五个线程全部阻塞导致了死锁,这是因为五个哲学家同时拿起了左手或右手的筷子引起的。导致死锁的原因就是上面提到的进程运行时请求和释放资源的顺序不当

避免死锁的方法:

1.至多只允许有4位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐。

2.仅当哲学家左右两只筷子同时可用时,才允许他拿起筷子进餐。

3.规定奇数号哲学家先拿他左边的筷子,然后再去拿他右边的筷子,而偶数号哲学家则相反

三.产生死锁的必要条件

1.互斥条件:进程对所分配到的资源进行排他性使用,在一段时间内某资源只能由一个进程占用。

2.请求和保持条件:进程已经获得了至少一个资源,但又提出了新的资源请求,但请求的这些资源不能立即获得,需要当前进程阻塞等待。

3.不剥夺条件:进程已获得的资源在未使用完之前不能被剥夺,只能在使用完由自己释放。

4.循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集
合{P0,P1,P2,…,Pn}中的 P0正在等待一个 P1占用的资源; P1正在等待 P2占用的资源,……,Pn正在等待已被 P0占用的资源。

四.死锁的处理方法

1.死锁忽略

因为当出现死锁时,忽略它的代价最小,所以Windows,Linux等许多操作系统都采用死锁忽略来处理死锁。

2.防止死锁

既然产生死锁需要上面四个必要条件,那我们就可以从上面四个必要条件下手。

1.破坏互斥条件:由设备的固有特性所决定的,通常无法改变这个条件。

2.破坏请求和保持条件:进程在开始运行之前,必须一次性地申请其在整
个运行过程所需的全部资源。

3.破坏不剥夺条件:某一进程已经占有的资源,可以抢占其他进程的资源

4.破坏循环等待条件:可以给每个资源编号,每个进程按照编号顺序访问资源。

3.避免死锁

安全状态:安全状态,是指系统能按某种进程顺序来为每个进程分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成。如果系统无法找到这样一个安全序列,则称系统处于不安全状态。

避免死锁的一个重要算法就是

银行家算法

它通过记录系统中的资源向量、最大需求矩阵、分配矩阵、需求矩阵,当一个进程申请使用资源的时候,银行家算法通过先 试探 分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。以此保证系统只在安全状态下进行资源分配,来避免死锁。

我们通过一个具体的例子来进行解释:

这里写图片描述

  • Process为进程号
  • Allocation为已为进程分配的各种资源数的向量
  • Need为该进程还需要的资源数向量
  • Available是系统当前各种可用资源数目的向量
  • 还有一个Work向量,表示当前系统可用的各种资源

各个进程还有一个Finish标记,初始值为false,它表示当前进程是否执行完毕。这张图表示系统当前这5个进程的资源分配情况。

问题:此时是否为安全状态?

算法步骤

1.查找是否存在某个进程,这个进程的Need需求向量<=系统当前可用资源向量,如果找不到就不是安全状态。此时Available = Work, 这里假设先找到了P0进程:Need(0,0,1,2) < Work(1,6,2,2)

2.Work= Allocation(0,0,3,2) + Work(1,6,2,2), 所以此时Work=[1,6,5,4]; 标志位Finish[0] = true

3.接着在剩下的几个进程中重复1,2两个步骤,直到所有线程的Finish标记位都为true(这样就找到了一个安全序列)或剩下的进程中找不到1中这样的进程,即系统当前可用资源满足不了剩下任何一个进程的需要(这样就是一个非安全状态)。

上面的例子中就不再赘述寻找的过程了,直接给出一种安全序列{P0,P3,P4,P1,P2},所以此时是安全状态。

五.参考资料

一句话+一张图说清楚——银行家算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值