1. 程式人生 > >用Linux C的互斥鎖機制解決哲學家就餐問題(簡單實現)

用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想要吃東西。如果他拿起了第五隻餐叉,就有可能發生死鎖。相反,如果他徵求服務生同意,服務生會讓他等待。這樣,我們就能保證下次當兩把餐叉空餘出來時,一定有一位哲學家可以成功的得到一對餐叉,從而避免了死鎖。