并发编程经典问题之哲学家进餐。本文简单介绍了问题模型,并提供了一种 Python3 的解决方式。
问题模型
- 问题描述
- 一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。
- 哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、 右两根筷子(一根一根地拿起)。
- 如果筷子已在他人手上,则需等待。
- 饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
- 问题分析
- 5 个哲学家与左右邻居对其中间筷子的访问是互斥关系。
- 5 个哲学家之间是异步关系。
- 那么解决方法有两个:一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或者死锁现象的发生。
- 本题的关键是如何让一个哲学家拿到左右两个筷子而不造成死锁或者饥饿现象。
Talk is cheap, show me code
- 解法一,一个筷子一把锁,所有筷子互斥,实现简单,但存在循环等待与死锁的可能。性能和饥饿情况如下所示:
# log 文件中是运行 100s 的结果
cat log | grep finish | awk '{print $1}'| sed 's/^Philosopher\[\([0-9]\)\].*$/\1/' | sort -n | uniq -c
78 0 # 0 号哲学家进餐 78 次
79 1 # 1 号哲学家进餐 79 次
78 2 # 2 号哲学家进餐 78 次
78 3 # 3 号哲学家进餐 78 次
77 4 # 4 号哲学家进餐 77 次
- 解法二,在解法一的基础上,把拿两个筷子的操作原子化,可以避免死锁问题。性能和饥饿情况如下所示:
# log 文件中是运行 100s 的结果
cat log | grep finish | awk '{print $1}'| sed 's/^Philosopher\[\([0-9]\)\].*$/\1/' | sort -n | uniq -c
66 0 # 0 号哲学家进餐 66 次
68 1 # 1 号哲学家进餐 68 次
65 2 # 2 号哲学家进餐 65 次
66 3 # 3 号哲学家进餐 66 次
63 4 # 4 号哲学家进餐 63 次
- 结论:解法一性能占优势,解法二 4 号哲学家出现轻微饥饿情况。解法二运行时间设置到 1000s 时结果如下,饥饿情况反而不明显了:
676 0
677 1
675 2
676 3
675 4
- 代码
from threading import Thread,Lock
from time import sleep
PHILO_NUM = 5
# 吃饭
def eat(num):
print("Philosopher[" + str(num) + "] start to eat")
sleep(1)
print("Philosopher[" + str(num) + "] finish eating")
# 思考
def think(num):
print("Philosopher[" + str(num) + "] start to think")
sleep(1)
print("Philosopher[" + str(num) + "] finish thinking")
# 解法一
def philosopher_1(num, locks):
print("Philosopher[" + str(num) + "] Started")
lock_left = locks[num]
lock_right = locks[(num + 1) % PHILO_NUM]
sleep(1)
while True:
lock_left.acquire()
lock_right.acquire()
eat(num)
lock_right.release()
lock_left.release()
think(num)
# 解法二
def philosopher_2(num, c_locks, p_lock):
print("Philosopher[" + str(num) + "] Started")
lock_left = c_locks[num]
lock_right = c_locks[(num + 1) % PHILO_NUM]
sleep(1)
while True:
p_lock.acquire()
lock_left.acquire()
lock_right.acquire()
p_lock.release()
eat(num)
lock_right.release()
lock_left.release()
think(num)
def main():
# 筷子锁
lock_chopsticks = []
for i in range(PHILO_NUM):
lock_chopsticks.append(Lock())
# 步调锁
lock_pick = Lock()
# 启动哲学家线程
thread_philo = []
for i in range(PHILO_NUM):
thread_philo.append(Thread(target=philosopher_1, args=(i, lock_chopsticks), daemon=True))
thread_philo[-1].start()
# thread_philo.append(Thread(target=philosopher_2, args=(i, lock_chopsticks, lock_pick), daemon=True))
thread_philo[-1].start()
input("\n\n==Enter Any Key To Exit==\n\n")
exit()
if __name__ == '__main__':
main()