首先說一下什麼是哲學家進餐問題,這是操作系統課程中一個經典的同步問題,
問題如下:如上圖,有6個哲學家和6根筷子(那個藍色部分表示哲學家,那個紫色長條部分表示筷子),他們分別被編了0~5的號!如果某個哲學家想要進餐的話,必須同時拿起左手和右手邊的兩根筷子才能進餐!哲學家進餐完畢之後,就放下手中拿起的兩根筷子!這樣其他哲學家就能拿這些筷子進餐了!
OK,這樣就可能存在一個死鎖問題,比如0號哲學家拿了0號筷子,1號哲學家拿了1號筷子!如此往復,最終的結果就是每個哲學家都只拿了1根筷子,每個人都無法進餐,同時也無法放下手中的筷子!這樣就產生了死鎖!
那麼死鎖該如何解決呢?很簡單,就是根據哲學家的編號!如果是偶數號的哲學家,則先拿右手邊筷子(小的號),再拿左手邊的筷子(大的號)。而奇數號的哲學家則剛好相反!這樣,就不會出現每個哲學家都只拿了一根筷子的情況出現了!
具體程序如何實現呢?代碼如下:
1 /* 說明,本程序是為了模擬實現哲學家進餐的問題,一共有6個哲學家和6根筷子 2 */ 3 #include<stdio.h> 4 #include<string.h> 5 #include<stdlib.h> 6 #include<unistd.h> 7 #include<errno.h> 8 #include<pthread.h> 9 10 #define B_SIZE 4096 11 #define NUM_P 6 12 13 typedef struct phi 14 { 15 pthread_mutex_t chopsticks[NUM_P]; //五根筷子 16 int num; //哲學家的編號 17 pthread_mutex_t num_lock; //哲學家編號的鎖 18 }Phi,*PPhi; 19 void * tfunc(void *arg) 20 { 21 PPhi sp = (PPhi)arg; 22 int phi_num; 23 int next_num; 24 25 /*讀取哲學家的編號*/ 26 phi_num = sp->num; 27 pthread_mutex_unlock(&sp->num_lock); 28 printf("No.%d philosopher has comed\n",phi_num); 29 /*下一根筷子*/ 30 next_num= phi_num+1>=NUM_P?phi_num+1-NUM_P:phi_num+1; 31 32 sleep(5); //所有線程統一睡眠5S,來等待其他線程創建完成 33 34 if(phi_num%2 == 1) //奇數號先拿大的,再拿小的 35 { 36 pthread_mutex_lock(&(sp->chopsticks[next_num])); 37 //printf("No.%d philosopher lock the No.%d chopstick\n",phi_num,next_num); 38 pthread_mutex_lock(&(sp->chopsticks[phi_num])); 39 //printf("No.%d philosopher lock the No.%d chopstick\n",phi_num,phi_num); 40 41 printf("No.%d philosopher has eated!\n",phi_num); 42 43 pthread_mutex_unlock(&(sp->chopsticks[next_num])); 44 //printf("No.%d philosopher unlock the No.%d chopstick\n",phi_num,next_num); 45 pthread_mutex_unlock(&(sp->chopsticks[phi_num])); 46 //printf("No.%d philosopher unlock the No.%d chopstick\n",phi_num,phi_num); 47 } 48 else //偶數號先拿小的,再拿大的 49 { 50 pthread_mutex_lock(&(sp->chopsticks[phi_num])); 51 //printf("No.%d philosopher lock the No.%d chopstick\n",phi_num,phi_num); 52 pthread_mutex_lock(&(sp->chopsticks[next_num])); 53 //printf("No.%d philosopher lock the No.%d chopstick\n",phi_num,next_num); 54 55 printf("No.%d philosopher has eated!\n",phi_num); 56 57 pthread_mutex_unlock(&(sp->chopsticks[phi_num])); 58 //printf("No.%d philosopher unlock the No.%d chopstick\n",phi_num,phi_num); 59 pthread_mutex_unlock(&(sp->chopsticks[next_num])); 60 //printf("No.%d philosopher unlock the No.%d chopstick\n",phi_num,next_num); 61 } 62 63 return (void*)0; 64 } 65 int main(int argc,char *argv[]) 66 { 67 int err; 68 pthread_t tid[NUM_P]; 69 char buf_err[B_SIZE]; //用於保存錯誤信息 70 void *retv; //子線程的返回值 71 Phi phis; 72 73 for(int loop=0;loop<NUM_P;loop++) 74 { 75 /*設置哲學家的編號*/ 76 pthread_mutex_lock(&phis.num_lock); 77 phis.num = loop; 78 79 /*創建子線程當作哲學家*/ 80 err = pthread_create(&tid[loop],NULL,tfunc,&phis); 81 if(0 != err) 82 { 83 memset(buf_err,0,B_SIZE); 84 sprintf(buf_err,"[create]:%s\n",strerror(err)); 85 fputs(buf_err,stderr); 86 exit(EXIT_FAILURE); 87 } 88 } 89 90 for(int loop=0;loop<NUM_P;loop++) 91 { 92 err = pthread_join(tid[loop],&retv); 93 if(0 != err) 94 { 95 memset(buf_err,0,B_SIZE); 96 sprintf(buf_err,"[join]:%s\n",strerror(err)); 97 fputs(buf_err,stderr); 98 exit(EXIT_FAILURE); 99 } 100 101 printf("Main:thread return the value: %d\n",(int)retv); 102 } 103 104 return 0; 105 }
在上面的程序中,我創建了一個結構體PHI!其中,chopsticks是定義的五個互斥鎖,用來表示5根筷子!num變量用來表示哲學家的編號,而那個num_lock表示對哲學家編號進行一個鎖定,這個為什麼要這麼設置,隨後會講到!
首先講主程序,就是創建NUM_P個子線程用來表示這麼多個哲學家!在子線程裡面,需要根據哲學家編號的奇偶性來選擇不同的拿筷子的方法!這就需要傳一個哲學家編號給這個子線程!通過什麼傳呢,就是PHI結構體中的num變量!這裡我們會碰到一個問題!就是如果在主線程裡面設置了num,隨後就創建了子線程,但是子線程還沒來得及讀出這個num的值,主線程已經開始了下一次的循環,那麼很有可能導致子線程讀到的num值並不是我們想要讓它讀到的!所以這裡就用到了num_lock這個鎖,主線程寫了num的值後,就把num_lock這個鎖給鎖定,然後子線程讀到num的之後,才把num_lock這個鎖給解鎖,然後主線程才能進行下一次循環!這樣就可以確保子線程讀到的編號就是我們想要給他傳的編號!
在子線程裡面,我首先輸出一句"No.%d philosopher has comed\n",表示某個子線程已經創建好了!然後就讓這個線程睡眠5S,等待其他子線程創建完畢(這個其實不需要的,但是我感覺得讓哲學家們大致是一起開始吃飯的麼。。。^_~)!然後就照著上面那個思路,偶數號的哲學家先拿右手邊筷子,再拿左手邊筷子,奇數號哲學家則正好相反!這樣就能避免死鎖了!
還有一個問題就是我在子線程裡面定義的這個next_num這個變量,這個用來表示哲學家拿的下一根筷子的值,因為最大編號的那個哲學家的下一根筷子是第0號筷子,所以我這裡用了一個if判斷來分辨!
最後程序的運行結果如下:
OK,就是這些了!希望能夠對學習操作系統的朋友們產生一些幫助!