当两个或多个哲学家检查互斥锁为 1 并同时关闭互斥锁并进入测试函数时会发生啥

Posted

技术标签:

【中文标题】当两个或多个哲学家检查互斥锁为 1 并同时关闭互斥锁并进入测试函数时会发生啥【英文标题】:What happens when two or more philosophers check the mutex to be 1 and at the same time down the mutex and enter test function当两个或多个哲学家检查互斥锁为 1 并同时关闭互斥锁并进入测试函数时会发生什么 【发布时间】:2020-06-23 14:22:05 【问题描述】:
#define N 5 /* number of philosophers */
#define LEFT (i + N−1) % N /* number of i’s left neighbor */
#define RIGHT (i + 1) % N /* number of i’s right neighbor */
#define THINKING 0 /* philosopher is thinking */
#define HUNGRY 1 /* philosopher is trying to get for ks */
#define EATING 2 /* philosopher is eating */

typedef int semaphore; /* semaphores are a special kind of int */

int state[N]; /* array to keep track of everyone’s state */
semaphore mutex = 1; /* mutual exclusion for critical regions */
semaphore s[N]; /* one semaphore per philosopher */

void philosopher(int i) /* i: philosopher number, from 0 to N−1 */

    while (TRUE) /* repeat forever */
    
        think(); /* philosopher is thinking */
        take forks(i); /* acquire two for ks or block */

        eat(); /* yum-yum, spaghetti */
        put forks(i); /* put both for ks back on table */
    


void take forks(int i) /* i: philosopher number, from 0 to N−1 */

    down(&mutex); /* enter critical region */
    state[i] = HUNGRY; /* record fact that philosopher i is hungry */
    test(i); /* tr y to acquire 2 for ks */
    up(&mutex); /* exit critical region */
    down(&s[i]); /* block if for ks were not acquired */


void put forks(i) /* i: philosopher number, from 0 to N−1 */

    down(&mutex); /* enter critical region */
    state[i] = THINKING; /* philosopher has finished eating */
    test(LEFT); /* see if left neighbor can now eat */
    test(RIGHT); /* see if right neighbor can now eat */
    up(&mutex); /* exit critical region */


void test(i) /* i: philosopher number, from 0 to N−1 */

    if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING)
    
        state[i] = EATING;
        up(&s[i]);
    

如您所见,在这段代码中,我们有一个互斥锁,它最初是一个互斥锁,这意味着没有哲学家在测试分叉是否空闲。当两个或多个哲学家同时检查互斥锁并碰巧看到互斥锁是一个并且两者同时在互斥锁下并进入该函数以测试分叉是否空闲时会发生什么?这是否会发生是我的问题?

【问题讨论】:

【参考方案1】:

如果您使用的是真正的互斥体和真正的信号量(如在 POSIX Pthreads 或 C11 §7.26 Threads <threads.h> 中找到的),那么您会发现互斥体确保您不会遇到“两个或多个哲学家检查同时互斥”。这就是“互斥”的意思。

但是,您没有使用“真正的互斥锁”或“真正的信号量”;您正在使用普通的int 作为“信号量”来实现您所谓的“互斥锁”。使用普通的int 不能保证互斥——而且你不能使用普通的 C 轻松实现它。因此,你可能有多个哲学家同时检查互斥锁的真正风险。如果程序是多线程的,代码是不安全的。

我们必须说“如果程序是多线程的”,因为该程序不是 MCVE (Minimal, Complete, Verifiable Example)(或 MRE 或 SO 现在使用的任何名称)或 SSCCE (Short, Self-Contained, Correct Example)。它缺少主程序,因此无法判断显示的代码是如何实际执行的。

【讨论】:

【参考方案2】:

您担心两位哲学家同时检查互斥锁的可能性是对的。为了使互斥体正常工作,必须以某种方式使其成为不可能。

如果您使用 C 库的互斥锁和信号量,C 库保证它不可能的。具体来说,在这段代码中:

void take_forks(int i) /* i: philosopher number, from 0 to N−1 */

    down(&mutex); /* enter critical region */

如果两个或多个线程同时在一个未锁定的互斥体上调用downdown 将锁定其中一个线程,并立即为该线程返回。所有其他线程将被“阻塞”,直到持有锁的线程通过调用up 释放它。然后其中一个等待的线程将收到锁,down 将在该线程中返回,依此类推。

现在,你写了

typedef int semaphore; /* semaphores are a special kind of int */

这让我觉得你没有使用 C 库的信号量,你被指派自己实现它们。你要知道这个用普通的C语言是做不到的的。事实上,用普通的机器语言是做不到的。您必须使用特殊的 atomic 机器指令来完成这项工作。¹ C 标准的 2011 年修订版包括用于访问这些指令的特殊功能;首先阅读stdatomic.h 的文档。

请注意,同步原语很难正确处理,即使对于专家来说也是如此。如果有任何方法可以使用其他人已经为您实现的信号量和/或互斥锁来编写此程序,那么您应该这样做。


¹ 如果您正在使用只有一个 CPU 的计算机——也就是说,它不能同时执行多个线程——并且您有能力禁用中断,那么原子机器指令并不严格有必要,但最好还是使用原子机器指令,这样编译后的程序即使移到具有多个 CPU 的计算机上也能正常工作。

【讨论】:

关于“这不能用普通的 C 来完成”,我不知道这在现代计算机系统上是否还能工作,但是互斥原语在硬件支持之前就已经在软件中实现了:en.wikipedia.org/wiki/Lamport%27s_bakery_algorithm @SolomonSlow 请注意“讨论”部分的最后一段:Lamport 的烘焙算法假定一个顺序一致性内存模型。很少有(如果有的话)语言或多核处理器实现这样的内存模型。 我认为明智地使用 C11 的 atomic_loadatomic_store 会使这个算法在现代硬件上听起来不错,但不要问我确切的问题哪些内存访问需要这种处理。

以上是关于当两个或多个哲学家检查互斥锁为 1 并同时关闭互斥锁并进入测试函数时会发生啥的主要内容,如果未能解决你的问题,请参考以下文章

在我的情况下是不是需要互斥锁[关闭]

如何检测哪个互斥锁为操作系统提供了最大的时间?

初识多线程__下

10、同步互斥机制3(进程通信)(操作系统笔记)

哲学家进餐问题

忙等待中的互斥