操作系统第六次实验报告——使用信号量解决哲学家进餐问题
0 个人信息
- 张樱姿
- 201821121038
- 计算1812
1 实验目的
- 通过编程进一步了解信号量。
2 实验内容
- 在服务器上用Vim编写一个程序:使用信号量解决任一个经典PV问题,测试给出结果,并对运行结果进行解释。
3 实验报告
3.1 选择哲学家进餐问题
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
3.2 伪代码
分析:放在圆桌上的筷子是临界资源,在一段时间内只允许一位哲学家的使用。为了实现对筷子的互斥使用,使用一个信号量表示一只筷子,由这五个信号量构成信号量数组:
semaphore chopstick[5]={1,1,1,1,1};
所有信号量均被初始化为1,则第i位哲学家的活动可描述为:
do{ /*当哲学家饥饿时,总是先拿起左边的筷子,再拿起右边的筷子*/ wait(chopstick[i]); //拿起左筷子 wait(chopstick[(i+1)%5]); //拿起右筷子 eat(); //进餐 /*当哲学家进餐完成后,总是先放下左边的筷子,再放下右边的筷子*/ signal(chopstick[i]); //放下左筷子 signal(chopstick[i+1]%5); //放下右筷子 think(); //思考 }while(true);
但是在上述情况中,假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0;而当他们再试图去拿右边的筷子时,都将因无筷子可拿造成无限的等待,产生死锁。
因此,需要对上述算法进行改进,限制仅当哲学家左右的两只筷子都可用时,才允许他拿起筷子进餐。可以利用AND 型信号量机制实现,也可以利用信号量的保护机制实现。利用信号量的保护机制实现的思想是通过记录型信号量mutex对取左侧和右侧筷子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。利用AND型信号量机制可获得最简洁的解法。
①使用记录信号量实现:
semaphore mutex = 1; // 这个过程需要判断两根筷子是否可用,并保护起来 semaphore chopstick[5]={1,1,1,1,1}; void philosopher(int i) { while(true) { /* 这个过程中可能只能由一个人在吃饭,效率低下,有五只筷子,其实是可以达到两个人同时吃饭 */ think(); //思考 wait(mutex); //保护信号量 wait(chopstick[(i+1)%5]); //请求右边的筷子 wait(chopstick[i]); //请求左边的筷子 signal(mutex); //释放保护信号量 eat(); //进餐 signal(chopstick[(i+1)%5]); //释放右手边的筷子 signal(chopstick[i]); //释放左手边的筷子 } }
②使用AND型信号量实现:
semaphore chopstick[5]={1,1,1,1,1}; do{ think(); //思考 Swait(chopstick[(i+1)%5],chopstick[i]); //请求筷子 eat(); //进餐 Ssignal(chopstick[(i+1)%5],chopstick[i]); //释放筷子 }while(true);
3.3 完整代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <stdint.h> 5 #include <stdbool.h> 6 #include <errno.h> 7 #include <unistd.h> 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <sys/ipc.h> 11 #include <sys/sem.h> 12 #include <sys/wait.h> 13 14 15 union semun 16 { 17 int val; 18 struct semid_ds *buf; 19 unsigned short *array; 20 struct seminfo *__buf; 21 }; 22 23 #define ERR_EXIT(m) \ 24 do { \ 25 perror(m); \ 26 exit(EXIT_FAILURE); \ 27 } while(0) 28 29 //申请一个资源 30 int wait_1chop(int no,int semid) 31 { 32 //资源减1 33 struct sembuf sb = {no,-1,0}; 34 int ret; 35 ret = semop(semid,&sb,1); 36 if(ret < 0) { 37 ERR_EXIT("semop"); 38 } 39 return ret; 40 } 41 42 // 释放一个资源 43 int free_1chop(int no,int semid) 44 { 45 //资源加1 46 struct sembuf sb = {no,1,0}; 47 int ret; 48 ret = semop(semid,&sb,1); 49 if(ret < 0) { 50 ERR_EXIT("semop"); 51 } 52 return ret; 53 } 54 55 //筷子是一个临界资源 56 #define DELAY (rand() % 5 + 1) 57 58 //相当于P操作 59 //第一个参数是筷子编号 60 //第二个参数是信号量编号 61 void wait_for_2chop(int no,int semid) 62 { 63 //哲学家左边的筷子编号和哲学家编号是一样的 64 int left = no; 65 //右边的筷子 66 int right = (no + 1) % 5; 67 68 //筷子值是两个 69 //操作的是两个信号量,即两种资源都满足,才进行操作 70 struct sembuf buf[2] = { 71 {left,-1,0}, 72 {right,-1,0} 73 }; 74 //信号集中有5个信号量,只是对其中的资源sembuf进行操作 75 semop(semid,buf,2); 76 } 77 78 //相当于V操作 ,释放筷子 79 void free_2chop(int no,int semid) 80 { 81 int left = no; 82 int right = (no + 1) % 5; 83 struct sembuf buf[2] = { 84 {left,1,0}, 85 {right,1,0} 86 }; 87 semop(semid,buf,2); 88 } 89 90 91 //哲学家要做的事 92 void philosophere(int no,int semid) 93 { 94 srand(getpid()); 95 for(;;) 96 { 97 #if 1 98 //当两只筷子都可用的时候,哲学家才能进餐 99 printf("%d is thinking\n",no); //思考中 100 sleep(DELAY); 101 printf("%d is hungry\n",no); //饥饿 102 wait_for_2chop(no,semid); //拿到两只筷子才能吃饭 103 printf("%d is eating\n",no); //进餐 104 sleep(DELAY); 105 free_2chop(no,semid); //释放两只筷子 106 #else 107 //可能会造成死锁 108 int left = no; 109 int right = (no + 1) % 5; 110 printf("%d is thinking\n",no); //思考中 111 sleep(DELAY); 112 printf("%d is hungry\n",no); //饥饿 113 wait_1chop(left,semid); //拿起左筷子(只要有一个资源就申请) 114 sleep(DELAY); 115 wait_1chop(right,semid); //拿起右筷子 116 printf("%d is eating\n",no); //进餐 117 sleep(DELAY); 118 free_1chop(left,semid); //释放左筷子 119 free_1chop(right,semid); //释放右筷子 120 #endif 121 } 122 } 123 124 125 int main(int argc,char *argv[]) 126 { 127 int semid; 128 //创建信号量集,其中有5个信号量 129 semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); 130 if(semid < 0) { 131 ERR_EXIT("semid"); 132 } 133 union semun su; 134 su.val = 1; 135 int i; 136 for(i = 0;i < 5; ++i) { 137 //第二个参数也是索引 138 semctl(semid,i,SETVAL,su); 139 } 140 //创建4个子进程 141 int num = 0; 142 pid_t pid; 143 for(i = 1;i < 5;++i) 144 { 145 pid = fork(); 146 if(pid < 0) 147 { 148 ERR_EXIT("fork"); 149 } 150 if(0 == pid) //子进程 151 { 152 num = i; 153 break; 154 } 155 } 156 //哲学家要做的事情 157 philosophere(num,semid); 158 return 0; 159 }
3.4 运行结果
3.5 解释运行结果
一开始,五个哲学家都在思考,一段时间后,3号哲学家饥饿,申请两只筷子满足后可以开始进餐,此时2号、4号和1号哲学家也饿了,但由于2号、4号与3号哲学家相邻,因此,他们需要等待3号进餐完毕放下筷子后,才可以进餐。而此时1号哲学家是可以进餐的。接着,0号哲学家饥饿,但由于筷子还在1号哲学家上,因此需要等待。然后3号哲学家进餐完毕,开始思考,4号哲学家可以进餐。1号哲学家进餐完毕,开始思考,2号哲学家可以进餐。3号哲学家饥饿,4号哲学家进餐完毕,开始思考,0号哲学家可以进餐。等2号哲学家进餐完毕,开始思考,3号哲学家可以开始进餐……
注意到当相邻的哲学家需要等到左右的筷子都可以使用时才可以进餐,因此如果哲学家是五位时,最多能有两位不相邻哲学家可以同时就餐。
4 References
- https://blog.csdn.net/Yun_Ge/article/details/89177918
- https://blog.csdn.net/u014304293/article/details/46004677