哲学家进餐问题实验报告_【心得篇】经典同步问题

本文探讨哲学家进餐问题,通过分析问题提出、解决策略及经典案例,阐述同步问题的重要性。通过代码展示如何避免死锁,讨论不同解决方案的效率和适用场景。
摘要由CSDN通过智能技术生成

c253ede910aea96c966c0975bd6044cb.png

问题提出

我不止一次地强调,计算机的资源是有限的,但人们会用大量任务压榨计算机。遗憾的是,对于那些十分有限的资源,这些任务不能同时调用它们,需要协调。(打个无伤大雅的比方,就像卫生间坑位有限,需要协调一样,你肯定无法想象多人共用一个坑位吧)。总而言之,协调好资源就是一种同步(让大家都把自己的“时钟”调到资源那儿)。

问题解决

如果卫生间没有坑位了,怎么办?等呗。如果等到坑位,就可以使用了(注意要把门关上)。最后,使用完毕要冲掉,然后释放坑位资源。

在代码层面,其实所谓的“P/V”操作就是这里的“等待”(wait())和“释放”(signal())。

在使用前,要等待有空闲资源,就是P()一下,或者说wait()一下;当等来资源的时候,就进入了程序的“临界区”,独自使用资源;使用完资源之后,就得释放资源,就是V()一下,或者说signal()一下(就像高铁上的灯指示一下,更新一下可用资源数量)。

经典案例

哲学家进餐问题。设想一张桌子上有5位哲学家,他们只做两件事:拿起筷子吃饭或者放下筷子思考,他们不会一边吃饭一边思考。有趣之处在于,桌子上共有5根筷子,与哲学家一起排成一个环,每位哲学家双手边都各有一根筷子。如下图:

2e4f038f682397bd0748c6d5c0ae99d7.png
新学的灵魂手绘(小圆头是哲学家,红线是筷子)

怎样用代码描述哲学家吃饭和思考的过程而不引起冲突{同时使用一根筷子}呢?让我们试着解决这个同步问题。

  1. 共用的资源是什么?
    很明显是筷子啊。但是这跟日常上厕所的问题不太一样,平时有个坑就能去,但在这里,只能用双手边的筷子,不能拿别人那儿的筷子。(可以拿邻居的。。)所以事实上,筷子和筷子之间是不同的,我们要给每根筷子一个信号量(就是一个数,类比卫生间门口有个指示坑位的数字),这个信号量的初始值是1,因为每根筷子都是独一无二的。
  2. 有哪些活动?相关约束是什么?
    吃饭和思考,吃饭前要拿筷子,吃完之后放下筷子;思考前要放下筷子,思考后等着下一顿饭。

于是我们就很快写出如下代码:

semaphore chopsticks[5]={1,1,1,1,1};

void Philosopher_i(){//i号哲学家的进程
    do{
        wait(chopsticks[i]);//等左手边的筷子
        wait(chopsticks[(i+1)%5]);//等右手边的筷子
        eat();//吃饭
        signal(chopsticks[(i+1)%5]);//放下右手的筷子
        signal(chopsticks[i]);//放下左手的筷子
        think();//思考
    }while(true);//像个没有感情的机器人继续循环
}

这段代码cover了前面讲过的所有要点,并且能够保证不产生冲突。

但是!你发现没有,在这种同步机制下,可能出现哲学家们没法吃饭、没法思考、面面相觑的情况。试想某个时刻,所有人都拿起了自己左手边的筷子,桌子上没有筷子能拿了,也没有人会放下,而每个人此时只有一根筷子,没法吃饭也没法思考——大家都懵了!“太默契了吧小老弟”

这种情况不算冲突,但是属于异常情形(术语叫“死锁”),所以要避免。如何避免呢?有很多方法,以下介绍最简单的一种。

我们先想想,什么时候会出现上面的死锁?在刚开始的时候,大家同时运行代码中的第一行"wait(chopsticks[i])"把左手边的筷子拿了起来,这就产生了死锁。那么,如果限定某一时刻桌子上只有一位哲学家能拿筷子,问题岂不就解决了?对的。我们不妨把这种拿筷子的权限抽象成一个单位的独占资源,这样就能很快写出如下代码:

semaphore chopsticks[5]={1,1,1,1,1};
semaphore mutex=1;//互斥量(抽象的独占资源)
void Philosopher_i(){//i号哲学家的进程
    do{
        wait(mutex);//等到只有我能拿筷子
        wait(chopsticks[i]);//等左手边的筷子
        wait(chopsticks[(i+1)%5]);//等右手边的筷子
        signal(mutex);//我拿完筷子了,让别人拿吧
        eat();//吃饭
        signal(chopsticks[(i+1)%5]);//放下右手的筷子
        signal(chopsticks[i]);//放下左手的筷子
        think();//思考
    }while(true);//像个没有感情的机器人继续循环
}

这种解决办法还算漂亮,但并不完美,因为那么大的桌子,每次只让一个人拿筷子,效率也太低了吧,事实上相隔较远的哲学家并不会产生冲突,可这样子处理也把他们给限制了。有没有更优美的解决方案呢?

当然有。不知道大家记不记得以前学组合数学的时候有个鸽巢原理(或者叫抽屉原理),就是说n+1只相同的袜子放在n个抽屉里,则至少有一个抽屉里能凑成一双。很好理解吧,考虑最坏情况——即每个抽屉里只有一只袜子,这样一共n只袜子,还有一只袜子不管放在哪个抽屉里都能凑成一双了。

如果我们把筷子当成袜子,最多几个人同时拿这5只筷子的时候,至少有一个人能拿到一双筷子呢?很显然答案是4。我们就把这个4当作拿筷子的名额,很容易写出代码如下:

semaphore chopsticks[5]={1,1,1,1,1};
semaphore mutex=4;//互斥量(拿筷子的名额)
void Philosopher_i(){//i号哲学家的进程
    do{
        wait(mutex);//等我抢到一个拿筷子的名额
        wait(chopsticks[i]);//等左手边的筷子
        wait(chopsticks[(i+1)%5]);//等右手边的筷子
        signal(mutex);//我拿完筷子了,释放一个名额
        eat();//吃饭
        signal(chopsticks[(i+1)%5]);//放下右手的筷子
        signal(chopsticks[i]);//放下左手的筷子
        think();//思考
    }while(true);//像个没有感情的机器人继续循环
}

其实这段代码和前一段代码的差别只有第二行,但在实际运行的时候,情况可大不相同。如果哲学家人数增多,显然后面的程序效率更高。

总结

为了更好地实现共享,必须解决“同步”和“互斥”这两种关系。以上案例展示了经典的处理方案,如果用更复杂的信号量机制,也可以有其他处理办法,但超出了目前考研的要求。感兴趣的朋友可以找来相关论文和教材进行研究。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值