用Linux C的互斥鎖機制解決哲學家就餐問題(簡單實現)
在1971年,著名的電腦科學家艾茲格·迪科斯徹提出了一個同步問題,即假設有五臺計算機都試圖訪問五份共享的磁帶驅動器。稍後,這個問題被託尼·霍爾重新表述為哲學家就餐問題。這個問題可以用來解釋死鎖和資源耗盡。
「哲學家就餐說明」:有五個哲學家共用一張圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個筷子,他們的生活方式是交替進行思考和就餐,通常,一個哲學家飢餓時就會試圖去取用其左右最靠近自己的筷子,只有拿到兩個筷子才能就餐,就餐完畢,放下筷子繼續思考!
但其中存在這一中問題,當五個人同時飢餓的時候,每個人拿著自己左邊或右邊的一隻筷子,就會產生「死鎖」狀態,當然我們可以給每個人加上時間,等待幾分鐘後就必須放下手中的筷子,這種情況貌似解決餓了死鎖問題,但是沒法避免所謂的「活鎖」,在計算機中,五條執行緒同時進入同一狀態,拿起五隻筷子。所以,這種情況在計算機資源分配就會是不合理的。
在實際的計算機問題中,缺乏餐叉可以類比為缺乏共享資源。計算機技術是常用的是所謂的資源加鎖,用來保證在某個時刻,資源只能被一個程式或一段程式碼訪問。當一個程式想要使用的資源已經被另一個程式鎖定,它就等待資源解鎖。當多個程式涉及到加鎖的資源時,在某些情況下就有可能發生死鎖。例如,某個程式需要訪問兩個檔案,當兩個這樣的程式各鎖了一個檔案,那它們都在等待對方解鎖另一個檔案,而這永遠不會發生。
所以在這裡我們用計算機中的五個條件變數來模擬五隻筷子的狀態,用五條執行緒模擬五個philosopher,他們所能進行的操作便是:
對於可能產生的死鎖問題,我們這裡採用一中解決的辦法,那就是隻有當哲學接的左右兩隻筷子均處於可用狀態時,才允許他拿起筷子。這樣就可以避免他們同時拿起筷子就餐,導致死鎖。while(1){ thinking(); //思考 take_forks(); //拿筷子 eating(); //吃飯 put_down_forks(); } //放下筷子
下面是利用Linux執行緒互斥鎖機制對這個問題的一種程式碼實現:
#include <unistd.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define phi_num 5 #define think_time 2 #define eat_time 1 #define left (phi_id+phi_num-1)%phi_num #define right (phi_id+1)%phi_num enum { think , hungry , eat } phi_state[phi_num]; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t state[phi_num]={PTHREAD_MUTEX_INITIALIZER}; void Do_think(int phi_id){ printf(" philosopher %d is thinking now !\n",phi_id); sleep(think_time); } void Do_eat(int phi_id){ printf("philosopher %d is eating now !\n",phi_id); sleep(eat_time); } void check_phi_state(int phi_id){ if(phi_state[phi_id]==hungry&&phi_state[left]!=eat&&phi_state[right]!=eat){ phi_state[phi_id]=eat; pthread_mutex_unlock(&state[phi_id]); } } void Do_take_forks(int phi_id){ pthread_mutex_lock(&mutex); phi_state[phi_id]=hungry; check_phi_state(phi_id); pthread_mutex_unlock(&mutex); pthread_mutex_lock(&state[phi_id]); } void Do_put_forks(int phi_id){ pthread_mutex_lock(&mutex); phi_state[phi_id]=think; check_phi_state(left); check_phi_state(right); pthread_mutex_unlock(&mutex); } void *philosopher(void *arg){ int phi_id=*(int *)arg; while(1){ Do_think(phi_id); Do_take_forks(phi_id); Do_eat(phi_id); Do_put_forks(phi_id); } return NULL; } int main(int argc, char *argv[]){ int num; pthread_t *phi=(pthread_t*)malloc(sizeof(pthread_t)*phi_num); int *id=(int *)malloc(sizeof(int)*phi_num); for(num=0;num<phi_num;num++){ id[num]=num; pthread_create(&phi[num],NULL,philosopher,(void*)(&id[num])); } for(num=0;num<phi_num;num++){ pthread_join(phi[num],NULL); } return 0; }
編譯:
clang -o philosopher philosopher.c -Wall -lpthread
當然還有其他的策略,比如至多允許有四位哲學家同時去拿左邊的筷子,最終可以保證至少有一位哲學家進餐,並在就餐完畢,釋放他的筷子資源,其他的philosopher就有就會可以就餐了。
還有一中策略就是引入服務生的概念(Wiki上):哲學家必須經過他的允許才能拿起餐叉。因為服務生知道哪隻餐叉正在使用,所以他能夠作出判斷避免死鎖。為了演示這種解法,假設哲學家依次標號為A至E。如果A和C在吃東西,則有四隻餐叉在使用中。B坐在A和C之間,所以兩隻餐叉都無法使用,而D和E之間有一隻空餘的餐叉。假設這時D想要吃東西。如果他拿起了第五隻餐叉,就有可能發生死鎖。相反,如果他徵求服務生同意,服務生會讓他等待。這樣,我們就能保證下次當兩把餐叉空餘出來時,一定有一位哲學家可以成功的得到一對餐叉,從而避免了死鎖。