文章目录
- 一:问题描述
- 方案一:
- 方案二:
- 方案三:
一:问题描述
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到左右两只筷子时才能进餐,进餐完毕,放下筷子继续思考。
那么问题来了,如何保证哲学家们的动作有序进行,而不会有人永远拿不到叉呢?
以下代码均以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操作是不会阻塞的。