编写C语言程序,模拟完成哲学家就餐问题,使用gcc编译并运行。
为了防止死锁的发生,可以对哲学家进程施加一些限制条件,比如:
1.至多允许四个哲学家同时进餐;
2.仅当一个哲学家左右两边的筷子都可用时才允许他抓起筷子;
3.对哲学家顺序编号,要求奇数号哲学家先抓左边的筷子,然后再转他右边的筷子,而偶数号哲学家刚好相反。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5
sem_t chopsticks[N];//5种信号量
pthread_mutex_t mutex;//互斥锁
int philosophers[N] = {0, 1, 2, 3, 4};//代表5个哲学家的编号
void *philosopher (void* arg)
{
int i = *(int *)arg;
int left = i;//左筷子的编号和哲学家的编号相同
int right = (i + 1) % N;//右筷子的编号为哲学家编号+1
printf("哲学家%d号思考\n", i);
sleep(1);
printf("哲学家%d号饿了\n", i);
pthread_mutex_lock(&mutex);
sleep(1);
sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0,能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);
sleep(1);
sem_wait(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子\n", i, right);
pthread_mutex_unlock(&mutex);//解锁
sleep(1);
printf("哲学家%d现在有两支筷子,开始进餐\n", i);
sleep(1);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sleep(1);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
return NULL;
}
int main ()
{
srand(time(NULL));
pthread_t philo[N];//信号量初始化
for(int i = 0; i < N;i++){
sem_init(&chopsticks[i], 0, 1);
}
pthread_mutex_init(&mutex,NULL);//初始化互斥锁
for (int i=0; i<N; i++){
pthread_create(&philo[i], NULL, philosopher, &philosophers[i]);//创建线程
}
for (int i=0; i<N; i++){
pthread_join(philo[i], NULL);//挂起线程
}
for (int i=0; i<N; i++){
sem_destroy(&chopsticks[i]);//销毁信号量
}
pthread_mutex_destroy(&mutex);//销毁互斥锁
printf("\n程序结束运行");
return 0;
}
为了拿到筷子,依次获取每把筷子的锁,先是左手边的,然后是右手边的。结束就餐时,释放掉锁。
假设每个哲学家都拿到了左手边的筷子,他们每个都会阻塞住,并且一直等待另一个筷子。具体来说,哲学家0拿到了筷子0,哲学家1拿到了筷子1,哲学家2拿到筷子2,哲学家3拿到筷子3,哲学家4拿到筷子4。所有的筷子都被占有了,所有的哲学家都阻塞着,并且等待另一个哲学家占有的筷子。
关键是如何让一个哲学家拿到左右两个筷子而不造成死锁或者饥饿现象。那么解决方法有两个 ,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或者死锁现象的发生。
解决方法:一个哲学家只有在两个邻居都不在进餐时才允许进入到进餐状态。对哲学家顺序编号,要求奇数号哲学家先抓左边的筷子,然后再转他右边的筷子,而偶数号哲学家刚好相反。如果筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿起两根筷子才可以进餐。
读进程和写进程并发地运行,由于并发必然导致异步性,因此“写数据”和“读数据”两个操作执行的先后顺序是不确定的。而实际应用中,又必须按照“写数据->读数据”的顺序来执行的。