Python实现哲学家就餐问题:多线程同步与死锁避免
本文介绍如何使用Python多线程解决经典的哲学家就餐问题。通过定义CountDownLatch和DiningPhilosophers类,利用Lock实现叉子资源的互斥访问,并采用非阻塞获取策略避免死锁。代码完整展示了哲学家思考、拿取、进食及释放叉子的全过程,并输出符合格式的行为记录列表。
问题背景与核心逻辑
哲学家就餐问题是操作系统中经典的同步互斥问题。场景设定为五位哲学家围坐圆桌,每人面前有一盘食物,左右各放一把叉子。哲学家的行为交替进行:思考或进餐。进餐时必须同时拿到左右两边的两把叉子,否则只能等待。代码的目标是模拟这一过程,并返回每位哲学家进食指定次数(n)的行为记录。
输入参数 n 表示每个哲学家需要进餐的次数,取值范围为 1 到 60。输出结果为一个列表,其中每个子列表格式为 [a, b, c],分别代表哲学家编号、叉子位置(1为左,2为右)以及行为类型(1为拿起,2为放下,3为吃面)。例如,[4, 2, 1] 表示4号哲学家拿起了右边的叉子。

关键类与同步机制
实现该问题的核心在于处理并发访问共享资源(叉子)时的竞争条件,并避免死锁。代码中定义了两个关键类:CountDownLatch 用于协调主线程与哲学家子线程的结束时机;DiningPhilosophers 继承自 threading.Thread,负责模拟单个哲学家的行为。
CountDownLatch 实现
该类用于等待所有哲学家完成指定次数的进餐。它维护一个计数器 count,初始值为需要完成的总任务数。通过 wait() 方法阻塞主线程,直到所有哲学家调用 count_down() 将计数器减至0。实现中使用了 threading.Condition 来保证线程安全,通过 acquire() 和 release() 控制锁的获取与释放,并使用 notifyAll() 唤醒等待线程。
DiningPhilosophers 行为模拟
每个哲学家线程在 run() 方法中循环执行以下逻辑:
- 尝试获取叉子:使用
acquire(blocking=False)非阻塞方式尝试获取左叉和右叉。这种方式可以有效避免死锁,因为如果无法同时拿到两把叉子,线程会释放已拿到的叉子并短暂休眠,而不是永久阻塞。 - 执行动作:若成功拿到两把叉子,则依次执行拿起左叉、拿起右叉、进食、放下左叉、放下右叉的操作。这些操作会将对应的行为记录放入
operate_queue中。 - 失败处理:若只拿到一把叉子,则立即释放该叉子;若一把都没拿到,则随机休眠一小段时间后重试。
- 计数递减:完成一次进餐后,调用
count_latch.count_down()通知主线程任务进度。
完整代码实现
以下是完整的 Python 实现代码,包含线程初始化、资源分配及结果收集逻辑:
import queue
import threading
import time
import random
class CountDownLatch:
def __init__(self, count):
self.count = count
self.condition = threading.Condition()
def wait(self):
try:
self.condition.acquire()
while self.count > 0:
self.condition.wait()
finally:
self.condition.release()
def count_down(self):
try:
self.condition.acquire()
self.count -= 1
self.condition.notifyAll()
finally:
self.condition.release()
class DiningPhilosophers(threading.Thread):
def __init__(self, philosopher_number, left_fork, right_fork, operate_queue, count_latch):
super().__init__()
self.philosopher_number = philosopher_number
self.left_fork = left_fork
self.right_fork = right_fork
self.operate_queue = operate_queue
self.count_latch = count_latch
def eat(self):
time.sleep(0.01)
self.operate_queue.put([self.philosopher_number, 0, 3])
def think(self):
time.sleep(random.random())
def pick_left_fork(self):
self.operate_queue.put([self.philosopher_number, 1, 1])
def pick_right_fork(self):
self.operate_queue.put([self.philosopher_number, 2, 1])
def put_left_fork(self):
self.left_fork.release()
self.operate_queue.put([self.philosopher_number, 1, 2])
def put_right_fork(self):
self.right_fork.release()
self.operate_queue.put([self.philosopher_number, 2, 2])
def run(self):
while True:
left = self.left_fork.acquire(blocking=False)
right = self.right_fork.acquire(blocking=False)
if left and right:
self.pick_left_fork()
self.pick_right_fork()
self.eat()
self.put_left_fork()
self.put_right_fork()
break
elif left and not right:
self.left_fork.release()
elif right and not left:
self.right_fork.release()
else:
time.sleep(0.01)
print(str(self.philosopher_number) + ' count_down')
self.count_latch.count_down()
if __name__ == '__main__':
operate_queue = queue.Queue()
fork1 = threading.Lock()
fork2 = threading.Lock()
fork3 = threading.Lock()
fork4 = threading.Lock()
fork5 = threading.Lock()
n = 1
latch = CountDownLatch(5 * n)
for _ in range(n):
philosopher0 = DiningPhilosophers(0, fork5, fork1, operate_queue, latch)
philosopher0.start()
philosopher1 = DiningPhilosophers(1, fork1, fork2, operate_queue, latch)
philosopher1.start()
philosopher2 = DiningPhilosophers(2, fork2, fork3, operate_queue, latch)
philosopher2.start()
philosopher3 = DiningPhilosophers(3, fork3, fork4, operate_queue, latch)
philosopher3.start()
philosopher4 = DiningPhilosophers(4, fork4, fork5, operate_queue, latch)
philosopher4.start()
latch.wait()
queue_list = []
for i in range(5 * 5 * n):
queue_list.append(operate_queue.get())
print(queue_list)代码运行与结果分析
在 __main__ 块中,程序初始化了五个 threading.Lock 对象代表五把叉子,并设置每个哲学家进餐次数 n=1。随后创建五个 DiningPhilosophers 线程实例,分别分配左右叉子资源(注意编号循环:0号哲学家左叉为5号,右叉为1号,以此类推)。所有线程启动后,主线程调用 latch.wait() 阻塞等待。当所有哲学家完成进餐并调用 count_down() 后,主线程继续执行,从 operate_queue 中按顺序取出所有行为记录并打印。
该实现通过非阻塞获取叉子的策略,有效避免了传统死锁问题。虽然代码中使用了 time.sleep 来模拟思考时间和竞争等待,但在实际生产环境中,应根据具体业务需求调整休眠时间或采用更高效的并发原语。此外,输出结果中的 operate_queue 记录了所有线程的操作顺序,可用于验证同步逻辑的正确性。
以上就是Python实现哲学家就餐问题实例代码的详细内容,更多关于Python多线程同步机制的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













