在哲學家就餐問題中,假設有五位哲學家圍坐在圓桌前,每位哲學家需要進行思考和進餐兩種活動。他們的思考不需要任何資源,但進餐需要使用兩根筷子(左右兩側各一根)。筷子是共享資源,哲學家們在進行進餐時需要競爭筷子,而且不能出現死鎖情況,即每位哲學家都能在有可能的情況下進餐。
問題示例:
假設有五位哲學家,編號為 0 到 4,圍坐在圓桌周圍,每位哲學家面前有一根筷子。他們的行為可以描述如下:
-
思考:哲學家在沒有筷子的時候,思考一段時間。
-
進餐:哲學家只有同時拿到左右兩邊的筷子才能進餐,進餐完成后放下筷子繼續思考。
解決方案概述:
- 狀態記錄:每位哲學家有三種狀態:思考、饑餓和進餐。
- 互斥保護:使用互斥鎖保護對狀態的訪問,確保狀態變化的原子性。
- 信號量:使用信號量控制每根筷子的使用,確保每位哲學家能同時拿到左右兩根筷子。
我們用一個數組 state 來記錄每一位哲學家的三個狀態,分別是在進餐狀態、思考狀態、饑餓狀態(正在試圖拿叉子)。
那么,一個哲學家只有在兩個鄰居都沒有進餐時,才可以進入進餐狀態。
第?i
?個哲學家的左鄰右舍,則由宏?LEFT
?和?RIGHT
?定義:
- LEFT?: ( i + 5 - 1 ) % 5
- RIGHT?: ( i + 1 ) % 5
比如 i 為 2,則?LEFT
?為 1,RIGHT
?為 3。
解決步驟:
-
定義全局變量和初始化:
- 定義哲學家狀態數組
state[]
,互斥鎖pthread_mutex_t mutex
和信號量數組sem_t chopsticks[]
。 - 初始化互斥鎖和每個筷子的信號量。
- 定義哲學家狀態數組
-
哲學家線程函數設計:
- 哲學家線程循環執行思考和進餐過程。
- 在饑餓狀態時,試圖獲取兩側筷子,獲取成功則進餐,否則等待。
- 進餐后釋放筷子,繼續思考。
-
獲取和釋放筷子的操作:
- 使用互斥鎖保護對狀態數組的修改,確保線程安全。
- 利用信號量控制筷子的獲取和釋放,避免死鎖和資源爭奪。
示例代碼:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define NUM_PHILOSOPHERS 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2int state[NUM_PHILOSOPHERS]; // 哲學家的狀態數組
pthread_mutex_t mutex; // 互斥鎖,保護對狀態數組的訪問
sem_t chopsticks[NUM_PHILOSOPHERS]; // 每根筷子的信號量數組void *philosopher(void *arg) {int id = *((int *)arg);while (1) {// 思考printf("哲學家 %d 正在思考。\n", id);sleep(rand() % 3 + 1); // 模擬思考時間// 進入饑餓狀態printf("哲學家 %d 餓了。\n", id);pthread_mutex_lock(&mutex);state[id] = HUNGRY;printf("哲學家 %d 嘗試拿起筷子。\n", id);test(id); // 嘗試獲取兩側的筷子pthread_mutex_unlock(&mutex);sem_wait(&chopsticks[id]); // 獲取筷子,如果沒有則阻塞// 進餐printf("哲學家 %d 開始進餐。\n", id);sleep(rand() % 3 + 1); // 模擬進餐時間// 放下筷子sem_post(&chopsticks[id]); // 放下左側筷子sem_post(&chopsticks[(id + 1) % NUM_PHILOSOPHERS]); // 放下右側筷子printf("哲學家 %d 放下筷子,開始思考。\n", id);}pthread_exit(NULL);
}void test(int id) {if (state[id] == HUNGRY && state[(id + NUM_PHILOSOPHERS - 1) % NUM_PHILOSOPHERS] != EATING&& state[(id + 1) % NUM_PHILOSOPHERS] != EATING) {state[id] = EATING;sem_post(&chopsticks[id]); // 左側筷子sem_post(&chopsticks[(id + 1) % NUM_PHILOSOPHERS]); // 右側筷子}
}int main() {pthread_t philosophers[NUM_PHILOSOPHERS];int ids[NUM_PHILOSOPHERS];// 初始化互斥鎖pthread_mutex_init(&mutex, NULL);// 初始化每根筷子的信號量for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {sem_init(&chopsticks[i], 0, 1); // 初始值為1,表示筷子可用}// 創建哲學家線程for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {ids[i] = i;pthread_create(&philosophers[i], NULL, philosopher, (void *)&ids[i]);}// 等待線程結束for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {pthread_join(philosophers[i], NULL);}// 清理資源pthread_mutex_destroy(&mutex);for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {sem_destroy(&chopsticks[i]);}return 0;
}
解釋和步驟:
1. 全局變量和初始化
state[]
數組:每個元素表示一個哲學家的狀態,初始為思考狀態。pthread_mutex_t mutex
:互斥鎖,保護對state[]
數組的訪問。sem_t chopsticks[NUM_PHILOSOPHERS]
數組:每根筷子對應一個信號量,初始為可用狀態。
2. 哲學家線程函數 philosopher
- 思考階段:每個哲學家在思考一段時間后進入饑餓狀態。
- 饑餓階段:哲學家試圖獲取左右兩根筷子,如果成功則進入進餐狀態,否則等待。
- 進餐階段:成功獲取筷子后進行進餐,一段時間后釋放筷子,回到思考階段。
3. test
函數
- 檢查能否進入進餐狀態:檢查當前哲學家及其左右鄰居的狀態,如果都是饑餓狀態且兩側筷子可用,則將當前哲學家狀態設置為進餐,并釋放左右兩根筷子。
4. 主函數 main
- 初始化:初始化互斥鎖和每根筷子的信號量。
- 創建線程:創建并啟動每個哲學家的線程。
- 等待線程結束:主線程等待所有哲學家線程執行完畢。
- 清理資源:銷毀互斥鎖和每根筷子的信號量。