问题描述
一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭,如图2-10所示。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、 右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
问题分析
1) 关系分析。5名哲学家与左右邻居对其中间筷子的访问是互斥关系。
2) 整理思路。显然这里有五个进程。本题的关键是如何让一个哲学家拿到左右两个筷子而不造成死锁或者饥饿现象。那么解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或者死锁现象的发生。
一共5个哲学家,编号0 ~4, 5支筷子编号也是0 ~4, 0号哲学家右手的筷子编号是0号,逆时针增加,哲学家的编号也是逆时针增加所以:
0号哲学家对应的是: 4号和1号筷子.
1号哲学家对应的是: 0号和2号筷子.
2号哲学家对应的是: 1号和3号筷子.
3号哲学家对应的是: 2号和4号筷子.
4号哲学家对应的是: 3号和0号筷子.
所以有宏定义:
#define left(phi_id) (phi_id+N-1)%N
#define right(phi_id) (phi_id+1)%N
N = 5
5支筷子对应5个互斥锁,所以:
pthread_mutex_t forks[N]={PTHREAD_MUTEX_INITIALIZER};
哲学家线程需要执行的动作是:
void take_forks(int id){
//获取左右两边的筷子
printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
pthread_mutex_lock(&forks[left(id)]);
pthread_mutex_lock(&forks[right(id)]);
//printf("philosopher[%d] take_forks...\n", id);
}
void put_down_forks(int id){
printf("philosopher[%d] is put_down_forks...\n", id);
pthread_mutex_unlock(&forks[left(id)]);
pthread_mutex_unlock(&forks[right(id)]);
}
void* philosopher_work(void *arg){
int id = *(int*)arg;
printf("philosopher init [%d] \n", id);
while(1){
thinking(id);
take_forks(id);
eating(id);
put_down_forks(id);
}
}
该算法存在以下问题:当五个哲学家都想要进餐,分别拿起他们左边筷子的时候(都恰好执行完
pthread_mutex_unlock(&forks[left(id)]);)
筷子已经被拿光了,等到他们再想拿右边的筷子的时候(执行
pthread_mutex_unlock(&forks[right(id)]);
)就全被阻塞了,这就出现了死锁。
为了防止死锁的发生,可以对哲学家进程施加一些限制条件,一种解决方案是:
原理:仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。
方法1:利用AND 型信号量机制实现:根据课程讲述,在一个原语中,将一段代码同时需
要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当
某些资源不够时阻塞调用进程;由于等待队列的存在,使得对资源的请求满足FIFO 的要求,
因此不会出现饥饿的情形。
但是我并没有找到and型信号量的定义,所以不能使用.
方法2:利用信号量的保护机制实现。通过信号量mutex对eat()之前的取左侧和右侧筷
子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。
void* philosopher_work(void *arg){
int id = *(int*)arg;
printf("philosopher init [%d] \n", id);
while(1){
thinking(id);
pthread_mutex_lock(&mutex);
take_forks(id);
pthread_mutex_unlock(&mutex);
eating(id);
put_down_forks(id);
}
}
这个代码有个问题就是同一时间只能有一个哲学家取筷子,效率比较低.但是可以防止死锁
另一种解决方案是:
至多四个人拿起左边筷子。。保证至少有一个人可以用餐,那么就能解决了,添加一个信号量room赋值等于4.代码如下.
/*************
* every philosopher is in while loop: thinking -> take_forks -> eating -> put_down_forks -> thingking
*
* 对于可能产生的死锁问题,我们这里采用一中解决的办法,那就是只有当哲学接的左右两只筷子均处于可用状态时,
* 才允许他拿起筷子。这样就可以避免他们同时拿起筷子就餐,导致死锁。
*
* 如果2号哲学家在吃饭那么1号和3号就必须是在思考.
*
*
*
*
*
************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define N 5 // five philosopher
#define T_EAT 5
#define T_THINK 5
#define N_ROOM 4 //同一时间只允许4人用餐
#define left(phi_id) (phi_id+N-1)%N
#define right(phi_id) (phi_id+1)%N
enum { think , hungry , eat }phi_state[N];
sem_t chopstick[N];
sem_t room;
void thinking(int id){
sleep(T_THINK);
printf("philosopher[%d] is thinking...\n", id);
}
void eating(int id){
sleep(T_EAT);
printf("philosopher[%d] is eating...\n", id);
}
void take_forks(int id){
//获取左右两边的筷子
//printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
sem_wait(&chopstick[left(id)]);
sem_wait(&chopstick[right(id)]);
//printf("philosopher[%d] take_forks...\n", id);
}
void put_down_forks(int id){
printf("philosopher[%d] is put_down_forks...\n", id);
sem_post(&chopstick[left(id)]);
sem_post(&chopstick[right(id)]);
}
void* philosopher_work(void *arg){
int id = *(int*)arg;
printf("philosopher init [%d] \n", id);
while(1){
thinking(id);
sem_wait(&room);
take_forks(id);
sem_post(&room);
eating(id);
put_down_forks(id);
}
}
int main(){
pthread_t phiTid[N];
int i;
int err;
int *id=(int *)malloc(sizeof(int)*N);
//initilize semaphore
for (i = 0; i < N; i++)
{
if(sem_init(&chopstick[i], 0, 1) != 0)
{
printf("init forks error\n");
}
}
sem_init(&room, 0, N_ROOM);
for(i=0; i < N; ++i){
//printf("i ==%d\n", i);
id[i] = i;
err = pthread_create(&phiTid[i], NULL, philosopher_work, (void*)(&id[i])); //这种情况生成的thread id是0,1,2,3,4
if (err != 0)
printf("can't create process for reader\n");
}
while(1);
// delete the source of semaphore
for (i = 0; i < N; i++)
{
err = sem_destroy(&chopstick[i]);
if (err != 0)
{
printf("can't destory semaphore\n");
}
}
exit(0);
return 0;
}
View Code
还有一种就是:
原理:规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号
的哲学家则相反.按此规定,将是1,2号哲学家竞争1号筷子,3,4号哲学家竞争3号筷子.即
五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获
得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲
学家会较先可以吃饭,因此不会出现饿死的哲学家
修改函数如下:
void take_forks(int id){
//获取左右两边的筷子
//printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
if((id&1) == 1){
sem_wait(&chopstick[left(id)]);
sem_wait(&chopstick[right(id)]);
}
else{
sem_wait(&chopstick[right(id)]);
sem_wait(&chopstick[left(id)]);
}
//printf("philosopher[%d] take_forks...\n", id);
}