经典同步问题之哲学家就餐

一:问题描述

五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到左右两只筷子时才能进餐,进餐完毕,放下筷子继续思考。
在这里插入图片描述

那么问题来了,如何保证哲学家们的动作有序进行,而不会有人永远拿不到叉呢?
以下代码均以C++伪代码的形式产出,基于信号量以及PV操作。

方案一:

#define N 5           //哲学家人数
semaphore fork[5];	  //信号量初值,且为1

void philos(int i)
{
    
    
	while(1)
	{
    
    
		think();              //哲学家思考
		P(fork[i]);           //拿左边的叉子
		P(fork[i + 1] % N);   //拿右边的叉子
		eat();                //进餐
		V(fork[i]);           //放下左边的叉子
		V(fork[i + 1] % N);   //放下右边的叉子
	}
}

上面的操作看似可以实现,但存在一种极端情况,假设5为哲学家都同时拿起左边的叉子,那么此时会发生死锁!,也就是说每一位哲学家都会阻塞在P(fork[i + 1] % N);这条语句上,很明显发生死锁!

方案二:

既然方案一会存在死锁的可能,那么我们引入互斥量来保证不发生死锁!

#define N 5           //哲学家人数
semaphore fork[5];	  //信号量初值,且为1
semaphore mutex;

void philos(int i)
{
    
    
	while(1)
	{
    
    
		think();              //哲学家思考
		P(mutex);             //进入临界区
		P(fork[i]);           //拿左边的叉子
		P(fork[i + 1] % N);   //拿右边的叉子
		eat();                //进餐
		V(fork[i]);           //放下左边的叉子
		V(fork[i + 1] % N);   //放下右边的叉子
		V(mutex);             //推出临界区
	}
}

上述程序中互斥量的作用在于,只要有一个哲学家进入了临界区,也就是准备拿叉子时,其他哲学家是互斥不可访问临界区的,这又当这个哲学家吃完了退出临界区后,其他哲学家才可以拿叉子进餐。
但是,该方案虽然可以让哲学家们按顺序吃饭,但是每次进餐只允许一位哲学家,从效率上讲不是最好的解决方案!

方案三:

基于方案二使用了互斥量使得效率低下,方案一问题在于所有哲学家同时拿起一边的叉子出现死锁的问题,基于这两点做改进,让偶数编号的哲学家先拿左边的再拿右边的,让奇数编号的哲学家先拿右边的叉子,再拿左边的叉子!

#define N 5           //哲学家人数
semaphore fork[5];	  //信号量初值,且为1

void philos(int i)
{
    
    
	while(1)
	{
    
    
		think();                  //哲学家思考
		if(i % 2 == 0)
		{
    
    
			P(fork[i]);           //拿左边的叉子
			P(fork[i + 1] % N);   //拿右边的叉子
		}
		else
		{
    
    
			P(fork[i + 1] % N);   //拿右边的叉子
			P(fork[i]);           //拿左边的叉子
		}
		eat();                    //进餐
		V(fork[i]);               //放下左边的叉子
		V(fork[i + 1] % N);       //放下右边的叉子
	}
}

上面的程序,在P操作时,根据哲学家的编号不同,拿起左右两边叉子的顺序不同。另外,V的操作上是不需要分支的,因为V操作是不会阻塞的。

猜你喜欢

转载自blog.csdn.net/qq_43727529/article/details/131253095