在计算机科学中,哲学家就餐问题是一个经典的并发编程问题,它描述了五位哲学家围坐在一张圆桌旁,每位哲学家面前有一个盘子,而桌子中央有一大碗意大利面。每位哲学家需要使用两把叉子来吃意大利面,但每把叉子只能被一位哲学家使用。由于哲学家们从不交谈,这可能导致一种危险的情况,即每位哲学家都拿着一把左叉子,永远等待另一把右叉子(或反之亦然),从而产生死锁。
本文将介绍一种简单的多线程解决方案,以避免哲学家们为了叉子而发生竞争,从而确保他们能够顺利地进餐。
给每位哲学家编号,从1到5。每位哲学家使用两把叉子,具体分配如下:
当一位哲学家吃完后,他可以释放其他正在等待的哲学家。因此,有以下规则:
代码实现相对简单。创建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);
这是进入条件的结束。
注意:所有进入条件都必须由一个公共的临界区保护,否则全局状态将不一致。同样,所有退出条件也必须受到保护,因此使用同一个临界区来实现这两个目的。
当代码完成进入条件时,意味着它要么有完全合法的权限运行,要么等待。让考虑一个案例,它将运行(通过哲学家吃饭模拟),最后在一段时间后完成吃饭。所以是时候深入研究退出条件了。
NULL
:HANDLE hndTmp = NULL;
sp[1]=sp[5]=0;
if(!sp[1] && !sp[2])
sp[1]=sp[2]=1;
hndTmp = hndEvents[2];
LeaveCriticalSection(&cs);
if(hndTmp != NULL) SetEvent(hndTmp);