【操作系统】哲学家进餐问题
问题描述
哲学家进餐问题是并发编程中的一个经典问题,描述了五位哲学家围坐在一张圆桌旁,他们的生活由思考和进餐组成。在圆桌上有五个盘子,每位哲学家面前一个盘子,盘子之间有一支叉子。哲学家进餐需要同时使用左右两支叉子。问题的核心是如何设计一种算法,使得哲学家们既不会饿死,也不会发生死锁。
问题难点
-
资源竞争:每位哲学家需要同时获取两支叉子才能进餐,这可能导致资源竞争。
-
死锁:如果每位哲学家都拿起左边的叉子并等待右边的叉子,系统将陷入死锁。
-
饥饿:某些哲学家可能长时间无法获取两支叉子,导致饥饿。
解决方案
1. 资源分级方案
为圆桌上的叉子分配一个顺序(例如编号),哲学家必须先拿起编号较小的叉子,再拿起编号较大的叉子。这样可以避免循环等待,从而防止死锁。
2. 限制同时进餐人数
限制最多只有四位哲学家可以尝试进餐,这样至少有一位哲学家可以成功拿起两支叉子并进餐。
3. 使用信号量控制资源
为每支叉子分配一个信号量,并使用信号量来控制叉子的获取和释放。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define NUM_PHILOSOPHERS 5// 信号量数组,表示每支叉子
sem_t forks[NUM_PHILOSOPHERS];void* philosopher(void* arg) {int id = *(int*)arg;int left_fork = id;int right_fork = (id + 1) % NUM_PHILOSOPHERS;while (1) {// 思考printf("Philosopher %d is thinking\n", id);sleep(1);// 饿了,尝试拿起叉子printf("Philosopher %d is hungry\n", id);// 确保先拿起编号较小的叉子if (left_fork < right_fork) {// 拿起左边的叉子sem_wait(&forks[left_fork]);printf("Philosopher %d picked up left fork %d\n", id, left_fork);// 拿起右边的叉子sem_wait(&forks[right_fork]);printf("Philosopher %d picked up right fork %d\n", id, right_fork);} else {// 拿起右边的叉子sem_wait(&forks[right_fork]);printf("Philosopher %d picked up right fork %d\n", id, right_fork);// 拿起左边的叉子sem_wait(&forks[left_fork]);printf("Philosopher %d picked up left fork %d\n", id, left_fork);}// 吃饭printf("Philosopher %d is eating\n", id);sleep(1);// 放下右边的叉子sem_post(&forks[right_fork]);printf("Philosopher %d put down right fork %d\n", id, right_fork);// 放下左边的叉子sem_post(&forks[left_fork]);printf("Philosopher %d put down left fork %d\n", id, left_fork);}
}int main() {pthread_t philosophers[NUM_PHILOSOPHERS];int ids[NUM_PHILOSOPHERS];// 初始化信号量for (int i = 0; i < NUM_PHILOSOPHERS; i++) {sem_init(&forks[i], 0, 1);}// 创建哲学家线程for (int i = 0; i < NUM_PHILOSOPHERS; i++) {ids[i] = i;pthread_create(&philosophers[i], NULL, philosopher, &ids[i]);}// 等待线程结束for (int i = 0; i < NUM_PHILOSOPHERS; i++) {pthread_join(philosophers[i], NULL);}// 清理信号量for (int i = 0; i < NUM_PHILOSOPHERS; i++) {sem_destroy(&forks[i]);}return 0;
}
关键修改点
-
资源分级:
-
在拿起叉子时,哲学家会检查左右叉子的编号,先拿起编号较小的叉子,再拿起编号较大的叉子。
-
这样可以确保至少有一个哲学家能够成功拿起两支叉子并进餐,从而打破循环等待条件,避免死锁。
-
-
条件判断:
-
使用
if (left_fork < right_fork)
来决定先拿起哪支叉子。 -
如果左边的叉子编号小于右边的叉子编号,则先拿起左边的叉子;否则,先拿起右边的叉子。
-