首先說一下什麼是哲學家進餐問題,這是操作系統課程中一個經典的同步問題,

問題如下:如上圖,有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,就是這些了!希望能夠對學習操作系統的朋友們產生一些幫助!