C语言实战——哲学家问题
问题描述
有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。
解决方法
方法一
- 通过互斥信号量mutex对哲学家进餐之前取左右两侧的筷子的操作进行保护,可以防止死锁的出现
- 也就是说,要达到的目的是,某位哲学家开始拿第一支筷子时,其他哲学家全部不准拿筷子(即使他已经饿了),只可以放下,直到这位哲学家拿到一双筷子之后,允许其他筷子被拿起,以此类推。
#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种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同
pthread_mutex_t mutex;//定义互斥锁
int philosophers[N] = {0, 1, 2, 3, 4};//代表5个哲学家的编号
void delay (int len)
{
int i = rand() % len;
int x;
while (i > 0)
{
x = rand() % len;
while (x > 0)
{
x--;
}
i--;
}
}
void *philosopher (void* arg)
{
int i = *(int *)arg;
int left = i;//左筷子的编号和哲学家的编号相同
int right = (i + 1) % N;//右筷子的编号为哲学家编号+1
while (1)
{
printf("哲学家%d正在思考问题\n", i);
delay(60000);
printf("哲学家%d饿了\n", i);
pthread_mutex_lock(&mutex);//加锁
sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);
sem_wait(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子\n", i, right);
pthread_mutex_unlock(&mutex);//解锁
printf("哲学家%d现在有两支筷子,开始进餐\n", i);
delay(60000);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
}
}
int main (int argc, char **argv)
{
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);//销毁互斥锁
return 0;
}
方法二
- 对他们的拿筷子的顺序进行规定
- 规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。所以将是 2,3 号哲学家竞争 3 号筷子,4,5 号哲学家竞争 5 号筷子。1 号哲学家不需要竞争。最后总会有一个哲学家能获得两支筷子而进餐。
#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种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同
int philosophers[N] = {0, 1, 2, 3, 4};//代表5个哲学家的编号
void delay (int len)
{
int i = rand() % len;
int x;
while (i > 0)
{
x = rand() % len;
while (x > 0)
{
x--;
}
i--;
}
}
void *philosopher (void* arg)
{
int i = *(int *)arg;
int left = i;//左筷子的编号和哲学家的编号相同
int right = (i + 1) % N;//右筷子的编号为哲学家编号+1
while (1) {
if(i % 2 == 0){
printf("哲学家%d正在思考问题\n", i);
delay(60000);
printf("哲学家%d饿了\n", i);
sem_wait(&chopsticks[right]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, right);
sem_wait(&chopsticks[left]);
printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, left);
delay(60000);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
}
else{
printf("哲学家%d正在思考问题\n", i);
delay(60000);
printf("哲学家%d饿了\n", i);
sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。
printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);
sem_wait(&chopsticks[right]);
printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, right);
delay(60000);
sem_post(&chopsticks[left]);
printf("哲学家%d放下了%d号筷子\n", i, left);
sem_post(&chopsticks[right]);
printf("哲学家%d放下了%d号筷子\n", i, right);
}
}
}
int main (int argc, char **argv)
{
srand(time(NULL));
pthread_t philo[N];
//信号量初始化
for (int i=0; i<N; i++)
{
sem_init(&chopsticks[i], 0, 1);
}
//创建线程
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]);
}
return 0;
}