操作系统第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号哲学家比较积极,他先开始了进食,举起了叉子5和1。同时在1号哲学家进食期间,他的左右邻座没有对齐进行 “干扰”
- 当1号哲学家吃完后,放回了叉子,并唤醒他饥饿的邻座 5和2。这时候哲学家5和2在1吃完后就能获得他们其中一只叉子,也可以开始进食了!!!
- 这时候2吃的比较慢,而5先吃完了,他放下了叉子重新开始了思考。同时5号唤醒旁边饥饿的邻座。不过他的邻座1号已经吃过了,现在还在思考中(不是hungry状态),能开吃的只有4号,于是4号也开始拿起了叉子准备开吃。
六、实验问题
- linux下的多线程错误:
需要在编译选项时,加入一个多线程:
- 更为简单的死锁解决:
限制允许进入桌子的哲学家数量:如果有N个叉子,但只有N-1个哲学家允许竞争,至少有一个会成功。
可能的实现:整数信号量,初始化为N-1