并行程序设计-使用OpenMP解决哲学家就餐问题

前言

使用OpenMP解决哲学家就餐问题,其实感觉和通过pthreads解决哲学家就餐问题的作业类似,核心算法不变,只需要改变创建线程的方法,而OpenMP比pthreads层次更高,编写一些并行行为更容易。

一、问题描述

五个沉默的哲学家围坐在一个圆桌旁,桌上放着几碗意大利面。叉子被放置在每一对相邻的哲学家之间。每个哲学家必须交替地思考和进餐。然而,哲学家只能在有左右叉子的情况下才能吃意大利面。每个叉子只能被一个哲学家拿着,所以一个哲学家只能在没有被另一个哲学家使用的情况下使用它。当一位哲学家吃完饭后,他们需要把两把叉子放下,这样其他人就可以享用这些叉子了。哲学家只能在他们有空的时候拿右手或左手的叉子,在拿两副叉子之前他们不能开始吃东西。每个哲学家有3种状态{thinking,trying,eating}。吃东西不受剩余的意大利面或胃空间的限制,假设有一个无限的供给和一个无限的需求。问题是如何设计一种行为准则(并发算法),以确保没有哲学家挨饿;也就是说,每一种都可以永远在吃饭和思考之间交替,假设没有一个哲学家可以知道其他人什么时候可能想吃饭或思考。

二、概要设计

编程语言使用OpenMP与C语言实现。本次实验采用多线程解决哲学家就餐问题。由于哲学家的刀叉是共享变量,所以使用信号量实现互斥。线程个数(-n)即为哲学家个数,同时为每个叉子定义对应的信号量。每个哲学家在试图使用餐叉的时候需要先进行sem_wait,当拿到两个餐叉后,哲学家就能eating,eating完之后使用sem_post解锁这两个餐叉。

三、避免死锁的策略

method1:

解法:允许最多 thread_count - 1 个哲学家同时进入餐厅尝试获取餐叉,剩下的一个哲学家只有在有人吃完出去的时候才进去。这样就能保证起码有一个人能够同时获得两个餐叉,从而eating,避免大家都处于trying的状态。这种解法也能理解为引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉。当同时有 thread_count - 1 个餐叉被请求时,若是其他哲学家请求最后一个餐叉,服务生会让他等待。

实现方法:增加一个信号量sem_waiter,其初始值为thread_count - 1,每个线程trying时会先sem_wait(&sem_waiter),在请求到两个餐叉并且eating后,再解锁两个叉子并使用sem_post(&sem_waiter)通知服务生。

method2:

解法:使用非对称解决方案。即单号的哲学家先拿起左边的叉子,接着右边的叉子;而双号的哲学家先拿起右边的叉子,接着左边的叉子。

实现方法:获取当前线程的编号,如果是奇数号线程就先请求左边的餐叉,然后请求右边的餐叉;如果是偶数好线程就先请求右边的餐叉,然后请求左边的餐叉。

四、实验

编译:gcc -g -Wall -fopenmp -o philosopher philosopher.c

运行:./philosopher -normal n 哲学家个数 ./philosopher -method1 n 哲学家个数 ./philosopher -method2 n 哲学家个数

五、分析

1. -normal

正常状态下哲学家遵守以下规则:

  • 哲学家在左边的叉子可用(没有其他人拿起)之前处于思考状态。如果左边的叉子可用,就拿起来。

  • 哲学家等待右边的叉子可用。如果右边的叉子可用,就拿起来。

  • 如果两个叉子都已经拿起来,开始吃意大利面,每次吃面都花费同样的时间。

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

  • 然后放下右边的叉子。

  • 开始思考(进入一个循环)

    但这种解法是失败的,当每个哲学家都拿起左侧的叉子,等待右侧的叉子可用时,就会进入死锁状态,每个哲学家将永远都在等待(右边的)另一个哲学家放下叉子。

2. -method1

至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将sem_waiter作为信号量,只允许thread_count - 1个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入sem_waiter的等待队列,根据FIFO的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。

3.-method2

规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子。即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲学家会较先可以吃饭,因此不会出现饿死的哲学家。

六、源代码

#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[])
{
 //获取线程个数,本实验中也代表哲学家人数
 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); 
 }

 //根据参数进入相应的处理函数
 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;
 //奇数先左叉后右叉,偶数先右叉后左叉
 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

哲学家就餐问题解释

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容