吃饭还是思考?这是个问题——从哲学家就餐理解多线程同步

1971年,著名的计算机科学家艾兹格·迪科斯彻提出了一个同步问题,即假设有五台计算机同时去访问五份共享的内存数据,系统应该如何处理?后来为了方便表述和理解,演变成了计算机科学中大名鼎鼎的“哲学家就餐”问题,用来诠释系统死锁和资源耗尽的情况。

多线程同步/互斥中常见的名词解释

在具体介绍哲学家就餐问题前,我们需要明白一些名词的含义:

  • 多并发:系统在运行过程中遇到短时间内遇到大量操作请求的情况,例如对CPU资源,寄存器的请求。
  • 多线程:多线程就是多个执行序列。使得cpu执行下这个序列,又执行下那个序列,不不断切换运行,也就是俗话中的“一心二用”。另外很多人都觉得多线程是为了同步完成多项任务,这个说法是不准确的。多线程实际上应该是提高资源使用效率来提高系统的效率。
  • 活锁:假设线程A和B都需要都需要使用进程,但两者的优先级相同,就会互相谦让导致谁都不过去,形成僵持。
  • 死锁:死锁是指多个线程因竞争资源而造成的一种互相等待的局面),如果操作者不加以干涉,所有相关的进程都会一直无法运行下去。
  • 信号灯:也叫在信号量。使用在多线程同步中,一个线程完成某个操作后通过信号告诉别的线程,其他线程才可以执行动作。
  • 互斥量:使用在多线程互斥中,当一个线程占用某个资源,那么别的线程就无法访问,直到该线程离开,其他线程才可以访问该资源

在理解了上述概念后,我们来具体看一下哲学家就餐问题到底说了个什么?

哲学家就餐问题

哲学家有五个哲学家围绕着一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,一个哲学家进行思考,吃东西的时候时便取用他左、右最靠近的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。

哲学家们遵循着一定的规则:

  1. 只有拿到两只筷子时,哲学家才能吃饭。
  2. 如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
  3. 任一哲学家在自己未拿到两只筷子吃饭前,不会放下手中拿到的筷子。

由于哲学家之间不会进行交流(线程彼此之间独立),就可能会导致某些哲学家们一直处于等待筷子的状态,也就是我们上文说到的死锁情况。又或者说,每个哲学家都有左边的筷子,一直都在等右边的筷子(相反亦可)。如果这样的话,即使不出现死锁,也会发生筷子不够用的情况(资源耗尽)。

无论哪一种情况都是设计者们不想遇到的,针对这些问题,也有相应的解决方案,目前最常用的主要有以下三种。

服务员机制

一个服务员受不了这帮哲学家们磨磨蹭蹭的,说:“都别等着了,我给你们分筷子!”由于服务员知道哪位哲学家正在使用筷子,所以可以避免出现死锁这样的问题发生。

服务员给哲学家依次从A到E编号。假设此时A和C在吃东西,则有四只筷子在使用中。B坐在A和C之间,所有筷子都不能使用,而D和E之间有一只空余的筷子。假设这时D想要吃东西。如果他拿起了第五只筷子,就有可能发生死锁。这个时候服务员会让他先等着。这样就可以保证,当两根筷子空余出来时,一定有一位哲学家可以成功的得到一对筷子,从而避免了死锁。

资源分级解法

资源分级解法和服务员机制有些类似,会为资源分配一个偏序或者分级的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,保证不会有两个无关资源同时被同一项工作所需要。

在哲学家就餐问题中,给筷子按照一定的规则编号,每一个哲学家总是先拿起左右两边编号较低的筷子,再拿编号较高的。用完筷子后,他总是先放下编号较高的筷子,再放下编号较低的。在这种情况下,当四位哲学家同时拿起他们手边编号较低的筷子时,只有编号最高的筷子留在桌上,从而第五位哲学家就不能使用任何一只筷子了。而且,只有一位哲学家能使用最高编号的筷子,所以他能使用两只筷子用餐。当他吃完后,他会先放下编号最高的筷子,再放下编号较低的筷子,从而让另一位哲学家拿起后边的这只开始吃东西。

这种方法也是目前最为实用的一种解法。

Chandy/Misra解法

这种方法是K. Mani Chandy和J. Misra在1984年提出的一个解法,允许任意的用户争用任意数量的资源。和其他解法不同的是,这里使用到的编号都是任意的,也是因为这个原因,该解法允许很大的并行性。

对每一对竞争一个资源的哲学家,新拿一个筷子,给编号较低的哲学家。每只筷子都是“干净的”或者“脏的”。最初,所有的筷子都是脏的。当一位哲学家要吃东西时,他必须从与他竞争的邻居那里得到。对每只他当前没有的筷子,他都发送一个请求。当拥有筷子的哲学家收到请求时,如果筷子是干净的,那么他继续留着,否则就擦干净并交出筷子。当某个哲学家吃东西后,他的筷子就变脏了。如果另一个哲学家之前请求过其中的筷子,那他就擦干净并交出筷子。


哲学家就餐问题实质上多线程使用中会出现的问题,其实在计算机中,多进程和多线程的并行互斥在实际运用中还有很多需要注意的地方,这些知识较为抽象,在学习的时候一定要搞清楚每个名词的含义,最好可以找代码动手敲一敲,看看运行的结果,学习编程最好的方式就是动手去做。

发表评论
留言与评论(共有 0 条评论)
   
验证码:

相关文章

推荐文章

'); })();