哲学家就相当于线程,叉子就相当于资源。每个线程需要获取特定的两个资源才可以执行“吃”操作。每个叉子只能被特定的两个线程访问,且访问叉子时是互斥的。假设数据结构设计一个信号量数组,5个元素代表5个叉子,每个信号量的初始值(最大值)为1 代表每个叉子只能被1个线程同时访问 ,即互斥。0号线程只能访问0号资源和1号资源,1号线程只能访问1号资源和2号资源......4号线程只能访问4号资源和0号资源。
那么有人这么设计,每个哲学家先拿右边的叉子,再拿左手边的叉子。如果无法同时拿起两个叉子就释放掉自己占用的资源。
这个算法有一定的缺陷,如果线程/进程的时间片大小和 takefork(i) 执行的时间一致,那么当CPU执行完“先拿右边的叉子”后时间片用完,切换到下一个就绪线程执行“先拿右边的叉子”...到最后所有线程都拿起了右边的叉子,无法拿起左边的叉子,最后有可能再一同释放,不断循环....
如果再此基础上添加一个条件,如果我要尝试拿叉子的时候把左右两个叉子一起锁住呢?如果锁不住就释放锁。即要么两个一起拿,要么就不拿叉子进入等待。
怎么实现呢,先理一理规则。
当自己需要进食的时候,需要判断左边的叉子和右边叉子的状态是否可用。如果可用则消耗掉这两把叉子资源,不可用则进行等待。所以,需要一个叉子的状态数组状态分为可用和不可用,由于数组本身的访问不具有互斥性,所以还需要一个互斥量来保护状态数组的访问;
#define N 5
#define LEFT i
#define RIGHT (i+1)%N
#define FREE 0
#defind USED 1
int state[N]; //记录每个叉子的状态,初始值为FREE
semaphore mutex ; //保护state[]的互斥访问,初始值为1
void philosopher(int i ) //第i个哲学家需要拿第i号和(i+1)%N号的叉子
{
bool couldeat = false;
while(!couldeat){
P(mutex); //访问state前需要P
if(state[LEFT] == FREE && state[RIGHT ] == FREE){
state[LEFT ] = USED ;
state[RIGHT ] = USED ;
V(mutex); //访问state完才可以释放
couldeat = true;
}else{
V(mutex);
....hunger(i); // 哲学家自身进入饥饿(阻塞)状态
}
}
....eat()...... //获取到两把叉子就可以执行自己的私有操作了
P(mutex); //访问state前需要P
state[LEFT ] = FREE ;
state[RIGHT ] = FREE ;
//如果左边的左边叉子可用,那么通知左边的哲学家可以就餐
if( state[(LEFT - 1)%N] ==FREE){
notice(LEFT);
}
//如果右边的右边叉子可用,那么通知右边的哲学家可以就餐
if( state[(RIGHT + 1)%N] ==FREE){
notice(RIGHT );
}
V(mutex);
}
上述代码是一叉子为资源的一个伪码。如果以哲学家为资源的呢代码该如何呢?思路是一样的,用一个状态数组表示哲学家的3种状态,思考、饥饿、就餐。其中,隔壁的哲学家在就餐意味着自己无法就餐,进入阻塞态。隔壁没有哲学家就餐才意味着自己身边的两个叉子可用。
其中用一个函数判断i号哲学家满不满足吃饭资格的函数:
void test_take_left_right_forks(int i){
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING){
//当隔壁的哲学家不在吃饭且自己处于饥饿时唤醒第i号哲学家就餐
state[i] == EATING;
V(s[i]);
}
}
代码框架如下图,主要需要实现的是take_forks(i);和 put_forks(i). ,think() 和 eat()是自己的私有操作。
void take_forks(int i ) { // i号哲学家试图拿叉子
P(mutex);
state[i] = HUNGRY; //让自己为饥饿状态
test_take_left_right_forks(i); //如果没叉子那么s[i]==0,有叉子则s[i] ==1
V(mutex);
P(s[i]); //没有叉子则阻塞自身
}
void put_forks(int i ) { // i号哲学家吃完并通知隔壁的哲学家
P(mutex);
state[i] = THINKING; //自己不处于吃饭状态啦!!!
test_take_left_right_forks(LEFT); //因为自己吃饭导致隔壁哲学家饥饿(阻塞),所以要尝试唤醒他
test_take_left_right_forks(RIGHT); //右边也一样
V(mutex);
}