Python实现哲学家就餐问题:多线程同步与死锁避免

沿途有光
沿途有光
发布时间:2026-10-12| 1人浏览过
原创

本文介绍如何使用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() 方法中循环执行以下逻辑:

  1. 尝试获取叉子:使用 acquire(blocking=False) 非阻塞方式尝试获取左叉和右叉。这种方式可以有效避免死锁,因为如果无法同时拿到两把叉子,线程会释放已拿到的叉子并短暂休眠,而不是永久阻塞。
  2. 执行动作:若成功拿到两把叉子,则依次执行拿起左叉、拿起右叉、进食、放下左叉、放下右叉的操作。这些操作会将对应的行为记录放入 operate_queue 中。
  3. 失败处理:若只拿到一把叉子,则立即释放该叉子;若一把都没拿到,则随机休眠一小段时间后重试。
  4. 计数递减:完成一次进餐后,调用 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多线程同步机制的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。