并行程序設(shè)計-使用OpenMP解決哲學家就餐問題

前言

使用OpenMP解決哲學家就餐問題,其實感覺和通過pthreads解決哲學家就餐問題的作業(yè)類似叉跛,核心算法不變,只需要改變創(chuàng)建線程的方法筷厘,而OpenMP比pthreads層次更高鸣峭,編寫一些并行行為更容易。

一酥艳、問題描述

五個沉默的哲學家圍坐在一個圓桌旁摊溶,桌上放著幾碗意大利面充石。叉子被放置在每一對相鄰的哲學家之間。每個哲學家必須交替地思考和進餐拉岁。然而惰爬,哲學家只能在有左右叉子的情況下才能吃意大利面。每個叉子只能被一個哲學家拿著撕瞧,所以一個哲學家只能在沒有被另一個哲學家使用的情況下使用它。當一位哲學家吃完飯后丛版,他們需要把兩把叉子放下,這樣其他人就可以享用這些叉子了锌半。哲學家只能在他們有空的時候拿右手或左手的叉子寇漫,在拿兩副叉子之前他們不能開始吃東西。每個哲學家有3種狀態(tài){thinking州胳,trying,eating}遍膜。吃東西不受剩余的意大利面或胃空間的限制,假設(shè)有一個無限的供給和一個無限的需求瓢颅。問題是如何設(shè)計一種行為準則(并發(fā)算法),以確保沒有哲學家挨餓翰意;也就是說信柿,每一種都可以永遠在吃飯和思考之間交替,假設(shè)沒有一個哲學家可以知道其他人什么時候可能想吃飯或思考渔嚷。

二、概要設(shè)計

編程語言使用OpenMP與C語言實現(xiàn)客年。本次實驗采用多線程解決哲學家就餐問題漠吻。由于哲學家的刀叉是共享變量量瓜,所以使用信號量實現(xiàn)互斥侥猩。線程個數(shù)(-n)即為哲學家個數(shù)抵赢,同時為每個叉子定義對應的信號量。每個哲學家在試圖使用餐叉的時候需要先進行sem_wait铅鲤,當拿到兩個餐叉后,哲學家就能eating鹏往,eating完之后使用sem_post解鎖這兩個餐叉。

三伊履、避免死鎖的策略

method1:

解法:允許最多 thread_count - 1 個哲學家同時進入餐廳嘗試獲取餐叉款违,剩下的一個哲學家只有在有人吃完出去的時候才進去。這樣就能保證起碼有一個人能夠同時獲得兩個餐叉插爹,從而eating,避免大家都處于trying的狀態(tài)力穗。這種解法也能理解為引入一個餐廳服務生,哲學家必須經(jīng)過他的允許才能拿起餐叉够坐。當同時有 thread_count - 1 個餐叉被請求時超全,若是其他哲學家請求最后一個餐叉,服務生會讓他等待嘶朱。

實現(xiàn)方法:增加一個信號量sem_waiter,其初始值為thread_count - 1脉课,每個線程trying時會先sem_wait(&sem_waiter)财异,在請求到兩個餐叉并且eating后,再解鎖兩個叉子并使用sem_post(&sem_waiter)通知服務生呈驶。

method2:

解法:使用非對稱解決方案疫鹊。即單號的哲學家先拿起左邊的叉子,接著右邊的叉子拆吆;而雙號的哲學家先拿起右邊的叉子,接著左邊的叉子霉晕。

實現(xiàn)方法:獲取當前線程的編號捞奕,如果是奇數(shù)號線程就先請求左邊的餐叉,然后請求右邊的餐叉颅围;如果是偶數(shù)好線程就先請求右邊的餐叉,然后請求左邊的餐叉扒俯。

四、實驗

編譯:gcc -g -Wall -fopenmp -o philosopher philosopher.c

運行:./philosopher -normal n 哲學家個數(shù) ./philosopher -method1 n 哲學家個數(shù) ./philosopher -method2 n 哲學家個數(shù)

五夺姑、分析

1. -normal

正常狀態(tài)下哲學家遵守以下規(guī)則:

  • 哲學家在左邊的叉子可用(沒有其他人拿起)之前處于思考狀態(tài)掌猛。如果左邊的叉子可用,就拿起來荔茬。

  • 哲學家等待右邊的叉子可用。如果右邊的叉子可用丐黄,就拿起來孔飒。

  • 如果兩個叉子都已經(jīng)拿起來,開始吃意大利面桂对,每次吃面都花費同樣的時間鸠匀。

  • 吃完后先放下左邊的叉子。

  • 然后放下右邊的叉子缀棍。

  • 開始思考(進入一個循環(huán))

    但這種解法是失敗的,當每個哲學家都拿起左側(cè)的叉子诽凌,等待右側(cè)的叉子可用時坦敌,就會進入死鎖狀態(tài)痢法,每個哲學家將永遠都在等待(右邊的)另一個哲學家放下叉子。

2. -method1

至多只允許四個哲學家同時進餐蘸炸,以保證至少有一個哲學家能夠進餐尖奔,最終總會釋放出他所使用過的兩支筷子穷当,從而可使更多的哲學家進餐淹禾。以下將sem_waiter作為信號量,只允許thread_count - 1個哲學家同時進入餐廳就餐铃岔,這樣就能保證至少有一個哲學家可以就餐,而申請進入餐廳的哲學家進入sem_waiter的等待隊列智嚷,根據(jù)FIFO的原則纺且,總會進入到餐廳就餐,因此不會出現(xiàn)餓死和死鎖的現(xiàn)象摇天。

3.-method2

規(guī)定奇數(shù)號的哲學家先拿起他左邊的筷子恐仑,然后再去拿他右邊的筷子;而偶數(shù)號的哲學家則相反裳仆。按此規(guī)定,將是1纯丸、2號哲學家競爭1號筷子静袖,3、4號哲學家競爭3號筷子坠陈。即五個哲學家都競爭奇數(shù)號筷子捐康,獲得后,再去競爭偶數(shù)號筷子解总,最后總會有一個哲學家能獲得兩支筷子而進餐。而申請不到的哲學家進入阻塞等待隊列刻盐,根FIFO原則,則先申請的哲學家會較先可以吃飯壤追,因此不會出現(xiàn)餓死的哲學家供屉。

六、源代碼

#include <stdio.h>
#include <stdlib.h>
#ifdef _OPENMP
#include <omp.h>
#endif
#include <semaphore.h>
#include <unistd.h>
#include <string.h>

sem_t *sem_fork;    //定義一組餐叉的信號量
sem_t sem_waiter;   //method1中用到的信號量伶丐,用于確保至少一個人不能進餐

void *Philo_normal();    //一般方法,會陷入死鎖
void *Philo_method1();   //方法1肛走,服務生方法
void *Philo_method2();   //方法2录别,非對稱解決方案

int Gene_random() {
 return rand()%91 + 10;
}

int main(int argc, char *argv[])
{
 //獲取線程個數(shù),本實驗中也代表哲學家人數(shù)
 long thread_count = strtol(argv[2], NULL, 10);

 sem_fork = malloc (thread_count * sizeof(sem_t));

 //初始化waiter信號量
 sem_init(&sem_waiter, 0, thread_count-1);

 //初始化所有叉子信號量
 for(long i = 0; i < thread_count; i ++ ){
 sem_init(&sem_fork[i], 0, 1); 
 }

 //根據(jù)參數(shù)進入相應的處理函數(shù)
 if(strcmp(argv[1], "-normal") == 0){
 # pragma omp parallel num_threads(thread_count)
 Philo_normal();
 }
 else if(strcmp(argv[1], "-method1") == 0){
 # pragma omp parallel num_threads(thread_count)
 Philo_method1();
 }
 else if(strcmp(argv[1], "-method2") == 0){
 # pragma omp parallel num_threads(thread_count)
 Philo_method2();
 }

 //銷毀餐叉信號量
 for(long i = 0; i < thread_count; i ++ ){
 sem_destroy(&sem_fork[i]);
 }

 //銷毀服務生信號量
 sem_destroy(&sem_waiter); 

 //釋放空間
 free(sem_fork);
 return 0;
}

//一般方法葫男,會陷入死鎖
void *Philo_normal(void)
{
 # ifdef _OPENMP
 long my_rank = omp_get_thread_num();
 long thread_count = omp_get_num_threads();
 # else
 long my_rank = 0;
 long thread_count = 1;
 # endif
 long my_left_fork, my_right_fork;     //表示每個哲學家左右的叉子
 my_left_fork = my_rank;
 my_right_fork = (my_rank + 1) % thread_count;
 while(1){
 printf("Philosopher %ld is thinking\n", my_rank);
 //把進程掛起一段時間梢褐, 單位是微秒(百萬分之一秒)赵讯;
 //usleep( (rand()%91 + 10)*1000 ); 
 //本來應該隨機掛起10-100ms,這里為了更快的檢測死鎖,將時間改為固定的50ms
 usleep(50);

 printf("Philosopher %ld is trying\n", my_rank);

 sem_wait(&sem_fork[my_left_fork]);
 sem_wait(&sem_fork[my_right_fork]);

 printf("Philosopher %ld is eating\n", my_rank);

 //usleep( (rand()%91 + 10)*1000 ); 
 usleep(100);

 sem_post(&sem_fork[my_left_fork]);
 sem_post(&sem_fork[my_right_fork]);
 }
}

//方法1边翼,服務生方法
void *Philo_method1(void)
{
 # ifdef _OPENMP
 long my_rank = omp_get_thread_num();
 long thread_count = omp_get_num_threads();
 # else
 long my_rank = 0;
 long thread_count = 1;
 # endif
 //哲學家左右的叉子
 long my_left_fork, my_right_fork;
 my_left_fork = my_rank;
 my_right_fork = (my_rank + 1) % thread_count;

 while(1){
 printf("Philosopher %ld is thinking\n", my_rank);
 //usleep( (rand()%91 + 10)*1000 );     //think 10-100 ms
 usleep(50);

 printf("Philosopher %ld is trying\n", my_rank);
 //添加一個waiter信號量確保每個時刻最少一個人沒有進去就餐
 sem_wait(&sem_waiter);

 sem_wait(&sem_fork[my_left_fork]);
 sem_wait(&sem_fork[my_right_fork]);

 printf("Philosopher %ld is eating\n", my_rank);
 //usleep( (rand()%91 + 10)*1000 );    //eating 10-100 ms
 usleep(100);

 sem_post(&sem_fork[my_left_fork]);
 sem_post(&sem_fork[my_right_fork]);

 sem_post(&sem_waiter);
 }
}

//方法2组底,非對稱解決方案
void *Philo_method2(void)
{
 # ifdef _OPENMP
 long my_rank = omp_get_thread_num();
 long thread_count = omp_get_num_threads();
 # else
 long my_rank = 0;
 long thread_count = 1;
 # endif
 //表示哲學家左右的叉子
 long my_left_fork, my_right_fork;
 //奇數(shù)先左叉后右叉,偶數(shù)先右叉后左叉
 if(my_rank % 2 == 1){
 my_left_fork = (my_rank + 1) % thread_count;
 my_right_fork = my_rank;
 }
 else{
 my_left_fork = my_rank;
 my_right_fork = (my_rank + 1) % thread_count;
 }
 while(1){
 printf("Philosopher %ld is thinking\n", my_rank);
 //usleep( (rand()%91 + 10)*1000 );     //think 10-100 ms
 usleep(50);

 printf("Philosopher %ld is trying\n", my_rank);

 sem_wait(&sem_fork[my_left_fork]);
 sem_wait(&sem_fork[my_right_fork]);

 printf("Philosopher %ld is eating\n", my_rank);
 //usleep( (rand()%91 + 10)*1000 );    //eating 10-100 ms
 usleep(100);

 sem_post(&sem_fork[my_left_fork]);
 sem_post(&sem_fork[my_right_fork]);
 }
}

七桶癣、參考

維基百科-Dining_philosophers_problem

哲學家就餐問題解釋

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饺鹃,隨后出現(xiàn)的幾起案子间雀,更是在濱河造成了極大的恐慌镊屎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件连锯,死亡現(xiàn)場離奇詭異用狱,居然都是意外死亡,警方通過查閱死者的電腦和手機摇展,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門溺忧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祟滴,你說我怎么就攤上這事歌溉。” “怎么了研底?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵榜晦,是天一觀的道長。 經(jīng)常有香客問我乾胶,道長,這世上最難降的妖魔是什么斩郎? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任喻频,我火速辦了婚禮,結(jié)果婚禮上锻煌,老公的妹妹穿的比我還像新娘。我一直安慰自己宋梧,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布释涛。 她就那樣靜靜地躺著倦沧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪局荚。 梳的紋絲不亂的頭發(fā)上愈污,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音首装,去河邊找鬼杭跪。 笑死,一個胖子當著我的面吹牛涧尿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缺亮,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼桥言,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了并鸵?” 一聲冷哼從身側(cè)響起扔涧,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粉铐,沒想到半個月后卤档,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡汤踏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年舔腾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哗脖。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扳还,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氨距,到底是詐尸還是另有隱情,我是刑警寧澤楞遏,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布首昔,位于F島的核電站,受9級特大地震影響拘荡,放射性物質(zhì)發(fā)生泄漏撬陵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一巨税、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驶兜,春花似錦、人聲如沸抄淑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽郑原。三九已至,卻和暖如春属愤,著一層夾襖步出監(jiān)牢的瞬間酸役,已是汗流浹背住诸。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工只壳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留暑塑,地道東北人吼句。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓事格,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驹愚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容