操作系统第6次实验报告:使用信号量解决进程互斥访问

零、个人信息

  • 姓名:陈韵
  • 学号:201821121053
  • 班级:计算1812

一、哲学家进餐问题

  • 问题描述:

 

上图表示该问题。有五个哲学家(P1-P5)围坐在一张圆形的桌子旁,只能够思考与吃饭。

他们有五个叉子(1-5)供他们进食,但在进食时,必须双手持有叉子。吃完后,把两个叉子放下,供邻座的哲学家进餐。

 

二、伪代码表示

  我们可以对上述描述进行一个简单的抽象:将每个哲学家都用以下的伪代码表示

 1 while true do{
 2     // 开始思考
 3     think;      
 4     
 5     // 有点饿了,试图拿起两边的叉子并开吃
 6     pick_up_left_fork();
 7     pick_up_right_fork();
 8     eat();
 9     put_down_right_fork();
10     put_down_left_fork();
11 
12     // 吃饱了,返回继续思考人生
13 }

 

  解释:正如代码所示,每个哲学家最初都在思考。过了一段时间,哲学家们饿了(hungry),想吃东西。

  在这点上,哲学家开始拿起两手边的叉子,一旦拿到了这两个叉子,便开始吃饭。吃饱了就放回叉子,继续思考。

 

三、解决问题:死锁

  上面的伪代码是每个哲学家都必须经历的,他们都先拿起左叉子再拿起右叉子。

  试想一下,如果每个哲学家都先拿起他们的左叉子,但此时却无法拿起他们的右叉子了——对他的邻座而言这是邻座的左叉子,已经被邻座拿走了。

  这种情况即为循环等待,是导致死锁的条件之一。

 

  那么我们要避免死锁问题,则要确保循环等待被打破。

  下面是可行的解决办法:

  • 不要让所有的哲学家一下子坐下来吃饭
  • 在关键部分拿起两根筷子
  • 第一根筷子的交替选择

 

这里给出解决死锁问题的伪码表示:

1.引入信号量机制, semaphore:s 有2个相关的原子性操作:

 

down(s) //若s是负数 等待, 当s>0时,增加s
up(s)   //增加s

 

  注:源码中我选择使用 sem_wait() 与 sem_post().

 

  2.声明2个信号量:mutex(二值信号量 提供相互排斥) 和 哲学家数组。以及1个状态标志。

1 mutex:    semaphore2 phil[5]:  semaphore phil[5]3 pflag[5]: {THINK, HUNGRY, EAT}

 

  3.之前伪码的改进:(需要同时拿起两边的筷子)

 1 while true do{
 2     // 开始思考
 3     think;      
 4     
 5     // 有点饿了,开始拿起两边的叉子开吃
 6     // 同时拿起两边的筷子
 7     pick_up(left_fork, right_fork);
 8     eat();
 9     put_down(left_fork, right_fork);
10 
11     // 吃饱了,返回继续思考人生
12 }

 

  4.核心部分:拿叉与放叉

  •   拿叉:检查邻座两边的状态,可以则声称自己饿了准备开吃。
  •   放叉:释放自己两边的叉子,并唤醒邻座。
 1 i: phnum //哲学家编号
 2 void take_fork(i)
 3   {
 4     //  进入临界区
 5     down(mutex);         
 6 
 7     pflag[i] = HUNGRY;
 8     test[i];
 9 
10     //  临界区出来
11     UP(mutex);
12     //准备开吃
13     DOWN(phil[i]); 
14    }
15 //如果此哲学家处于等待状态, 让其开吃
16 void prepare(i)            
17 {
18       //检查邻座的状态,可以则声称自己想吃
19     if ( pflag[i] == HUNGRY && pflag[i-1] != EAT && pflag[i+1] != EAT)
20        then
21         {
22           pflag[i] = EAT;
23           UP(s[i])
24          }
25 }
26 
27 
28 //吃完饭后,释放资源:左右两边的叉子,并唤醒等待的邻座
29 void put_fork(i)
30 {
31     //进入临界区
32     DOWN(mutex);                
33 
34     //叫醒左边  和  右边
35     test(i-1);               
36     test(i+1);               
37 
38     //退出临界区 
39     UP(mutex);                  
40 }

 

四、完整源代码

  1 #include <pthread.h>
  2 #include <semaphore.h>
  3 #include <stdio.h>
  4 #include <unistd.h>
  5 
  6 #define N 5
  7 #define THINKING 2
  8 #define HUNGRY 1
  9 #define EATING 0
 10 #define LEFT (phnum + 4) % N
 11 #define RIGHT (phnum + 1) % N
 12 
 13 int state[N];                  //状态:思考2,饥饿1,吃饭0
 14 int phil[N] = {0, 1, 2, 3, 4}; //哲学家编号
 15 
 16 sem_t mutex; //二值信号量
 17 sem_t S[N];  //哲学家信号量数组
 18 
 19 //是否准备开吃
 20 void prepare(int phnum)
 21 {
 22     if (state[phnum] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING)
 23     {
 24         //准备开吃
 25         state[phnum] = EATING;
 26 
 27         sleep(2);
 28 
 29         printf("哲学家 %d 拿起了叉子 %d 和 %d\n",
 30                phnum + 1, LEFT + 1, phnum + 1);
 31 
 32         printf("哲学家 %d 开吃啦 \n", phnum + 1);
 33 
 34         // 唤醒等待的哲学家
 35         sem_post(&S[phnum]);
 36     }
 37 }
 38 
 39 // 拿起叉子
 40 void take_fork(int phnum)
 41 {
 42 
 43     sem_wait(&mutex);
 44 
 45     // 状态设为饥饿
 46     state[phnum] = HUNGRY;
 47 
 48     printf("哲学家 %d 有点饿了...\n", phnum + 1);
 49 
 50     // 准备
 51     prepare(phnum);
 52 
 53     sem_post(&mutex);
 54 
 55     sem_wait(&S[phnum]);
 56 
 57     sleep(1);
 58 }
 59 
 60 // 放下叉子
 61 void put_fork(int phnum)
 62 {
 63 
 64     sem_wait(&mutex);
 65 
 66     // 放下叉子 开始思考
 67     state[phnum] = THINKING;
 68 
 69     printf("哲学家 %d 放下了叉子 %d 和 %d \n",
 70            phnum + 1, LEFT + 1, phnum + 1);
 71     printf("哲学家 %d 开始了思考...\n", phnum + 1);
 72 
 73     //放下叉子后开始思考,同时唤醒旁边两位哲学家
 74     prepare(LEFT);
 75     prepare(RIGHT);
 76 
 77     sem_post(&mutex);
 78 }
 79 
 80 void *philospher(void *num)
 81 {
 82 
 83     while (1)
 84     {
 85 
 86         int *i = num;
 87 
 88         sleep(1);
 89 
 90         take_fork(*i); //拿起叉子
 91 
 92         sleep(0);
 93 
 94         put_fork(*i); //放下叉子
 95     }
 96 }
 97 
 98 int main()
 99 {
100 
101     int i;
102     pthread_t thread_id[N]; //线程
103 
104     // 创建二值信号量
105     sem_init(&mutex, 0, 1);
106     // 创建哲学家信号量
107     for (i = 0; i < N; i++)
108         sem_init(&S[i], 0, 0);
109 
110     for (i = 0; i < N; i++)
111     {
112 
113         // 创建哲学家线程
114         pthread_create(&thread_id[i], NULL,
115                        philospher, &phil[i]);
116 
117         printf("哲学家 %d 开始了思考...\n", i + 1);
118     }
119 
120     for (i = 0; i < N; i++)
121         pthread_join(thread_id[i], NULL);
122 }

 

五、结果解释

  1.给出程序运行结果:

  .......很......多......

 

   2.部分运行结果的解释:

 

  1.     起初每一位哲学家都开始了思考,并且他们都开始了饥饿...
  2.   这时候1号哲学家比较积极,他先开始了进食,举起了叉子5和1。同时在1号哲学家进食期间,他的左右邻座没有对齐进行 “干扰”
  3.   当1号哲学家吃完后,放回了叉子,并唤醒他饥饿的邻座 5和2。这时候哲学家5和2在1吃完后就能获得他们其中一只叉子,也可以开始进食了!!!
  4.   这时候2吃的比较慢,而5先吃完了,他放下了叉子重新开始了思考。同时5号唤醒旁边饥饿的邻座。不过他的邻座1号已经吃过了,现在还在思考中(不是hungry状态),能开吃的只有4号,于是4号也开始拿起了叉子准备开吃。

六、实验问题

  • linux下的多线程错误:

 

需要在编译选项时,加入一个多线程: 

 

 

  • 更为简单的死锁解决: 

限制允许进入桌子的哲学家数量:如果有N个叉子,但只有N-1个哲学家允许竞争,至少有一个会成功。

可能的实现:整数信号量,初始化为N-1

posted @ 2020-05-30 23:15  韵韵韵  阅读(340)  评论(0编辑  收藏  举报