2.3 经典IPC问题
2.3.1 哲学家进餐问题
在此不再赘述问题的内容。
最浅显的解法,think思考完毕取左叉,取右叉,吃面,放左叉,放右叉,一共五个步骤。遗憾的是,这种解法是步错误的。如果所有哲学家同时拿起左面的叉子,他们都拿不到右面的叉子,于是发生死锁。
若将程序修改一下,规定在拿左叉后查看右叉是否可用,如果不可用,则先放下左叉,过一段时间再重复整个过程。这种方法实际上也是错误的。可能某一瞬间所有哲学家同时启动这个算法,一起拿起左叉再放下,过一段时间又重复这个过程。
可能有人想到:如果哲学家在拿不到右叉时等待一段随机的时间,而不是等待相同的时间,则长时间处于上述死锁状态的几率就会很小了。这种想法是对的,而且在大多数应用中,稍后再试的方法也不成问题。例如,在使用以太网的局域网中,一台计算机只有在检测到其他计算机都没有在发送数据包的时候发送数据包。但是由于传输延迟,两台由电缆相连的电脑可能交叠发送数据包,从而发生数据包碰撞。当检测到数据包碰撞时,每台电脑等待一段随机的时间后重试。实际上,这一方案做的很好。但在一些应用中人们希望一种完全正确的方案,它不能因为一串靠不住的随机数字而失效(想想核电站中的安全控制系统)。
将该算法进行如下改进即可基部发生死锁又不发生饥饿:使用一个二进制信号量对think函数后的五个步骤进行保护。在哲学家开始拿叉子之前,先对信号量mutex执行down操作。放回叉子后,再对mutex进行up操作。从理论上来讲,是可行的的。但从实际角度来讲,这里有性能上的局限:同一时刻只能有一个哲学家进餐。而五把叉子实际上允许两个哲学家同时进餐。
正确的解法应该对任意多个哲学家で情况都能获得最大的并行度。其中使用一个数组state来跟踪一个哲学家是在吃饭、思考还是在试图拿叉子:一个哲学家只有在两个邻居都不在进餐状态下才能进入到进餐状态。第i位哲学家的邻居由宏LEFT和RIGHT定义。换言之,若i为1,则LEFT为1,RIGHT为3。
哲学家进餐问题的解决方案使用了一个信号量数组,每个信号量分别对应一个哲学家,这样,当所有叉子被占用时,想进餐的哲学家可以阻塞。注意,每个进程将过程philosopher作为主代码运行,而其他take_forks、pul_forks和test只是普通的过程,而非单独的进程。