哲学家就餐问题的多线程解决方案

在计算机科学中,哲学家就餐问题是一个经典的并发编程问题,它描述了五位哲学家围坐在一张圆桌旁,每位哲学家面前有一个盘子,而桌子中央有一大碗意大利面。每位哲学家需要使用两把叉子来吃意大利面,但每把叉子只能被一位哲学家使用。由于哲学家们从不交谈,这可能导致一种危险的情况,即每位哲学家都拿着一把左叉子,永远等待另一把右叉子(或反之亦然),从而产生死锁

本文将介绍一种简单的多线程解决方案,以避免哲学家们为了叉子而发生竞争,从而确保他们能够顺利地进餐。

解决方案

给每位哲学家编号,从1到5。每位哲学家使用两把叉子,具体分配如下:

  • 哲学家1使用叉子1和叉子5。
  • 哲学家2使用叉子1和叉子2。
  • 哲学家3使用叉子2和叉子3。
  • 哲学家4使用叉子3和叉子4。
  • 哲学家5使用叉子4和叉子5。

当一位哲学家吃完后,他可以释放其他正在等待的哲学家。因此,有以下规则:

  • 哲学家1只能释放哲学家2或哲学家5。
  • 哲学家2只能释放哲学家3或哲学家1。
  • 哲学家3只能释放哲学家4或哲学家2。
  • 哲学家4只能释放哲学家5或哲学家3。
  • 哲学家5只能释放哲学家1或哲学家4。

代码实现相对简单。创建5个线程,每个线程代表一位哲学家,模拟吃饭和思考的过程。虽然每位哲学家的代码基本相同,但为了提高可读性,重复了代码。

多线程架构

构建了一个多线程架构,试图解决这类问题。以下是一些可能的问题:

  • 哲学家就餐问题
  • 读者-写者问题

每个运行条件的线程都必须有进入条件和退出条件。在这些条件之间,线程安全的有条件代码可以执行。将使用这种架构深入探讨读者和写者问题,并在本文中解释“哲学家就餐问题”的解决方案。

使用一个CRITICAL_SECTION变量来同步所有线程必须执行的进入和退出条件。有五个事件句柄,每个线程一个,需要在必要时等待,还有一个五元素整数数组表示叉子。每个元素要么是1要么是0(布尔值),0表示叉子没有被任何哲学家使用,1表示叉子正在被某位哲学家使用。除了这些,还有其他数据成员,但它们仅用于动画,因此不会深入讨论。

每个哲学家线程将不断在while(1)循环中旋转。在循环的开始,有一个局部变量nShouldWait,一个布尔值,用于决定线程状态是等待还是执行。

以下是进入条件的步骤:

  • 进入临界区:EnterCriticalSection(&cs);
  • 检查叉子是否准备好吃饭:if(!sp[1] && !sp[5])
  • 如果叉子准备好了,将叉子变量设置为占用:sp[1]=sp[5]=1;
  • 因此不需要等待:nShouldWait=0;
  • 否则,叉子不空闲,其他哲学家正在使用,所以不能吃饭:nShouldWait=1;
  • 离开临界区:LeaveCriticalSection(&cs);
  • 如果nShouldWait为真,则等待事件,直到被信号运行:WaitForSingleObject(hndEvents[1],INFINITE);

这是进入条件的结束。

注意:所有进入条件都必须由一个公共的临界区保护,否则全局状态将不一致。同样,所有退出条件也必须受到保护,因此使用同一个临界区来实现这两个目的。

当代码完成进入条件时,意味着它要么有完全合法的权限运行,要么等待。让考虑一个案例,它将运行(通过哲学家吃饭模拟),最后在一段时间后完成吃饭。所以是时候深入研究退出条件了。

  • 定义一个局部句柄并将其初始化为NULLHANDLE hndTmp = NULL;
  • 因为已经吃完了,所以放弃叉子:sp[1]=sp[5]=0;
  • 由于哲学家1只能释放给哲学家2或哲学家5。首先检查是否可以释放哲学家2:if(!sp[1] && !sp[2])
  • 如果为真,则将他的叉子设置为占用状态:sp[1]=sp[2]=1;
  • 并设置他的事件句柄,他正在等待吃饭:hndTmp = hndEvents[2];
  • 如果不是,检查是否可以释放哲学家5,并重复上述步骤。
  • 最后离开临界区:LeaveCriticalSection(&cs);
  • 允许哲学家2或哲学家5吃饭:if(hndTmp != NULL) SetEvent(hndTmp);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485