操作系统原理:哲学家就餐经典问题

18
五月
2021

 

哲学家就相当于线程,叉子就相当于资源。每个线程需要获取特定的两个资源才可以执行“吃”操作。每个叉子只能被特定的两个线程访问,且访问叉子时是互斥的。假设数据结构设计一个信号量数组,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);

}

 

 

 

 

 

 

TAG

网友评论

共有访客发表了评论
请登录后再发布评论,和谐社会,请文明发言,谢谢合作! 立即登录 注册会员