针对5位哲学家就餐问题,采用奇数号哲学家先拿起左边的筷子,再去拿右边的筷子;而偶数号哲学家则用相反的方法,进行解决。假设每位哲学家思考5秒,进餐3秒,给出100秒内每位哲学家进餐的总次数。
1. 原理说明
0、1、2、3、4为哲学家
⓪、①、②、③、④为筷子
哲学家就餐问题是一种一个进程需要两个临界资源的问题。
如果每位哲学家都先拿自己左边的筷子,那么此时没有冲突,每位哲学家都可以拿到自己左边的筷子。
但是接下来每位哲学家都去拿自己右边的筷子时,右边的筷子已经被右边的哲学家拿走了,那么所有的哲学家就只能等着自己右边的哲学家把其左边的筷子放下才能继续就餐,因此整个程序会陷入永久阻塞。
这种每个进程都在等待其他进程释放资源或者是等待其他进程给出所需数据而无限期陷入僵持的状态,称为死锁。
为了解决哲学家就餐问题的死锁问题,可以规定:
奇数号哲学家先拿其左边的筷子,再拿其右边的筷子;
偶数号哲学家先拿其右边的筷子,再拿其左边的筷子。
也就是说:
哲学家0先拿①再拿⓪
哲学家1先拿①再拿②
哲学家2先拿③再拿②
哲学家3先拿③再拿④
哲学家4先拿⓪再拿④
首先,每个奇数号的筷子会被相邻的两个哲学家争夺,然后,抢夺成功的哲学家才能继续去拿另一边的筷子,这种竞争保证了必会有胜出者得到两只筷子,避免了出现上述的死锁问题。
2. 代码说明
奇数号哲学家:思考问题——拿左筷子——拿右筷子——进餐——放下筷子
偶数号哲学家:思考问题——拿右筷子——拿左筷子——进餐——放下筷子
思考5秒:sleep(5)
进餐3秒:sleep(3)
拿左筷子:sem_wait(&chopsticks[left]);
拿右筷子:sem_wait(&chopsticks[right]);
放下筷子:sem_post(&chopsticks[left]); sem_post(&chopsticks[right]);
下面说明一下如何控制只记录前100秒内的进餐次数。
需要用到定义的两个时间戳全局变量:time_t start,end;
start记录程序开始运行的时间,end记录当时时间,用difftime(end,start)表示end时到start时程序运行了多久,即当时到程序开始运行时间经历了多少秒,如果此时的运行时间超过了时间限制TIME,则停止运行。
正常情况下,一个哲学家的活动是这样的:思考——拿一只筷子——拿另一只筷子——进餐——放下筷子。由于记录的是进餐次数,所以可以在这一系列的活动结束后判断程序运行时间是否超过时间限制,如果没有超过就eattime++,相当于顺利进餐一次,如果超过了就意味着在这个过程中的某个时间点已经到了时间限制TIME,应该跳出循环,此次进餐不计入进餐次数中。
3. 运行结果
由于我的代码给出的结果是5个哲学家的整个进餐过程,所以运行结果很长,考虑篇幅不宜过长,我只截取了开头部分和结尾部分,100秒内每个哲学家的进餐次数在结尾部分给出。
如果不需要整个过程都输出可以把循环中打印的语句全部删除,循环结束后直接打印次数即可。
还有就是如果不想等100s,可以等比例缩短时长,比如:时长限制改为10s,那么进餐和思考的时间应该改为:0.3s、0.5s。
czxt2022@ubuntu:~$ gcc 1.c -o 1 -lpthread
czxt2022@ubuntu:~$ ./1
哲学家0正在思考
哲学家3正在思考
哲学家2正在思考
哲学家1正在思考
哲学家4正在思考
哲学家0拿起了1号筷子
哲学家0拿起了0号筷子,开始第1次进餐
哲学家2拿起了3号筷子
哲学家2拿起了2号筷子,开始第1次进餐
哲学家0放下了0号筷子
哲学家0放下了1号筷子
现在已经过了8秒
……
现在已经过了102秒
时间到。哲学家4吃了11次
哲学家2放下了2号筷子
哲学家2放下了3号筷子
现在已经过了104秒
时间到。哲学家2吃了12次
哲学家3拿起了3号筷子
哲学家3拿起了4号筷子,开始第13次进餐
哲学家0放下了0号筷子
哲学家0放下了1号筷子
现在已经过了105秒
时间到。哲学家0吃了12次
哲学家1拿起了1号筷子
哲学家1拿起了2号筷子,开始第13次进餐
哲学家3放下了3号筷子
哲学家3放下了4号筷子
现在已经过了107秒
时间到。哲学家3吃了12次
哲学家1放下了1号筷子
哲学家1放下了2号筷子
现在已经过了108秒
时间到。哲学家1吃了12次
4. 源代码
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5 //N为科学家人数
#define TIME 10.0 //TIME为时间限制
sem_t chopsticks[N]; //设置5种信号量,对应5只筷子
int philosophers[N] = {0, 1, 2, 3, 4}; //代表5个哲学家的编号
time_t start,end; //记录时间
void *philosopher (void* arg)
{
int eattime=0; //进餐次数
int i = *(int *)arg; //获得传递进来的参数,也就是哲学家的编号
int left = i; //左筷子的编号和哲学家的编号相同
int right = (i + 1) % N; //右筷子的编号为哲学家编号+1
while (1)
{
if(i % 2 == 0)
{//偶数号哲学家
printf("哲学家%d正在思考问题\n", i);
sleep(5);
printf("哲学家%d饿了\n", i);
sem_wait(&chopsticks[right]);//左筷子的信号量-1之后>=0时,表示能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, right);
sem_wait(&chopsticks[left]);
printf("哲学家%d拿起了%d号筷子,现在有两支筷子,开始第%d次进餐\n", i, left,eattime+1);
sleep(3);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
end=time(NULL);
printf("现在已经过了%d秒\n",(int)difftime(end,start));
if(difftime(end,start)>TIME)break;//如果超过时间限制,直接退出,此次进餐作废
eattime++;
}
else
{//奇数号哲学家
printf("哲学家%d正在思考问题\n", i);
sleep(5);
printf("哲学家%d饿了\n", i);
sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);
sem_wait(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子,现在有两支筷子,开始第%d次进餐\n", i,right, eattime+1);
sleep(3);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
end=time(NULL);
printf("现在已经过了%d秒\n",(int)difftime(end,start));
if(difftime(end,start)>TIME)break; //如果超过时间限制,直接退出,此次进餐作废
eattime++;
}
}
printf("时间到。哲学家%d吃了%d次\n",i,eattime);
}
int main (int argc, char **argv)
{
pthread_t philo[N];
int i;
start=time(NULL);
//信号量初始化
for (i=0; i<N; i++) sem_init(&chopsticks[i], 0, 1);
//创建线程
for (i=0; i<N; i++) pthread_create(&philo[i], NULL, philosopher, &philosophers[i]);
//挂起线程
for (i=0; i<N; i++) pthread_join(philo[i], NULL);
//销毁信号量
for (i=0; i<N; i++) sem_destroy(&chopsticks[i]);
return 0;
}