问题描述:
有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。
约束条件
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃饭前,不会放下手中拿到的筷子。
哲学家进餐问题是一大类并发控制问题的典型例子,涉及信号量机制、以及死锁等操作系统中关键问题的应用。
最直接的解决办法(引起死锁)
最直接的解决办法是5只筷子分别设置为初始值为1的互斥信号量。这样可以保证不会有相邻的哲学家同时进餐,但是若5位哲学家同时拿起左筷子,都会因为拿不到右筷子而引起死锁。
优化解决办法
解决办法有很多种:
(1)通过互斥信号量 mutex 对哲学家进餐之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现。(当第i个哲学家将左右筷子都拿到了才允许其他哲学家拿筷子)
如下面的运行截图可以说明。哲学家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 sem_t chopsticks[N];//设置5种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同 sem_t r;//最多允许有r(4)个哲学家同时拿起左筷子 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(50000); printf("哲学家%d饿了\n", i); sem_wait(&r);//如果前4个哲学家同时拿起左筷子,第五个不能同时拿起左筷子,保证至少有一位哲学家能吃到饭,解决(死锁状态)。 sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。 printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left); sem_wait(&chopsticks[right]); printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, right); delay(50000); sem_post(&chopsticks[left]); printf("哲学家%d放下了%d号筷子\n", i, left); sem_post(&chopsticks[right]); printf("哲学家%d放下了%d号筷子\n", i, right); sem_post(&r); } } 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); } sem_init(&r, 0, 4); //创建线程 for (int i=0; i<N; i++) { pthread_create(&philo[i], NULL, (void *)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]); } sem_destroy(&r); return 0; }
(2)规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。所以将是 2,3 号哲学家竞争 3 号筷子,4,5 号哲学家竞争 5 号筷子。1 号哲学家不需要竞争。最后总会有一个哲学家能获得两支筷子而进餐。
例如下面的运行结果截图可以说明。当哲学家4放下4号筷子时,4号筷子被释放,3号哲学家得到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 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(50000); 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(50000); 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(50000); 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(50000); 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, (void *)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; }
(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种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同 sem_t r;//最多允许有r(4)个哲学家同时拿起左筷子 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(50000); printf("哲学家%d饿了\n", i); sem_wait(&r);//如果前4个哲学家同时拿起左筷子,第五个不能同时拿起左筷子,保证至少有一位哲学家能吃到饭,解决(死锁状态)。 sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。 printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left); sem_wait(&chopsticks[right]); printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, right); delay(50000); sem_post(&chopsticks[left]); printf("哲学家%d放下了%d号筷子\n", i, left); sem_post(&chopsticks[right]); printf("哲学家%d放下了%d号筷子\n", i, right); sem_post(&r); } } 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); } sem_init(&r, 0, 4); //创建线程 for (int i=0; i<N; i++) { pthread_create(&philo[i], NULL, (void *)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]); } sem_destroy(&r); return 0; }