Concurrency 为什么同时写入的布尔值在设置为false后仍然为true?

Concurrency 为什么同时写入的布尔值在设置为false后仍然为true?,concurrency,go,mutex,dining-philosopher,Concurrency,Go,Mutex,Dining Philosopher,我正在用围棋写一个解决方案。我的解决方案很简单:检查两个叉子是否都可用。如果是的话,两个都选。如果不是,就让这两个都去吧 然而,我遇到了一个奇怪的并发错误,即fork的可用性在显式设置为false之后仍然为true。MyFork声明如下: type Fork struct { mu sync.Mutex avail bool } func (f *Fork) PickUp() bool { f.mu.Lock() if f.avail == false {

我正在用围棋写一个解决方案。我的解决方案很简单:检查两个叉子是否都可用。如果是的话,两个都选。如果不是,就让这两个都去吧

然而,我遇到了一个奇怪的并发错误,即fork的可用性在显式设置为false之后仍然为true。My
Fork
声明如下:

type Fork struct {
    mu    sync.Mutex
    avail bool
}

func (f *Fork) PickUp() bool {
    f.mu.Lock()
    if f.avail == false {
        f.mu.Unlock()
        return false
    }
    f.avail = false
    fmt.Println("set false")
    f.mu.Unlock()
    return true
}

func (f *Fork) PutDown() {
    f.mu.Lock()
    f.avail = true
    f.mu.Unlock()
}
当哲学家调用pick()时,程序将等待互斥锁;如果此时fork可用,
fork
将其可用性布尔值设置为false,并返回true以指示操作成功

哲学家是这样写的:

type Philosopher struct {
    seatNum int
}

func (phl *Philosopher) StartDining(forkList [9]Fork) {
    for {
        fmt.Println(forkList[phl.seatNum], phl.seatNum)
        if forkList[phl.seatNum].PickUp() {
            fmt.Println("Philo ", phl.seatNum, " picked up fork ", phl.seatNum)

            if forkList[phl.getLeftSpace()].PickUp() { 
                fmt.Println("Philo ", phl.seatNum, " picked up fork ", phl.getLeftSpace())
                fmt.Println("Philo ", phl.seatNum, " has both forks; eating...")
                time.Sleep(5 * time.Second)

                forkList[phl.seatNum].PutDown()
                forkList[phl.getLeftSpace()].PutDown()
                fmt.Println("Philo ", phl.seatNum, " put down forks.")
            } else {
                forkList[phl.seatNum].PutDown()
            }
        }
    }
}
(注意:
getLeftSpace()
函数被排除在外,因为它的实现与此无关;它只获取左向空间的索引。)

哲学家的实现非常简单:它检查是否可以得到第一个fork。然后,它检查是否可以得到第二个叉子;如果不行,它就放下第一把叉子。如果可以,它将两个都保持5秒钟,然后将两个都放下。出于测试的目的,我将此限制为两位哲学家


然而,这并不能正常工作。哲学家0拿起第一个叉子,然后拿起第二个叉子。我已验证这些货叉的可用性已设置为false。此时,哲学家1被互斥锁锁定。但是,一旦Philo 0释放互斥锁,Philo 1就会进入该函数

此时的预期结果是
PickUp()
函数返回false;由于叉子不再可用,因此无法拾取。但是,该函数不这样做;它返回true并允许Philo 1拾取叉子

更神秘的是,当Philo 1拿起叉子时,叉子的可用性是
true
,即使Philo 0显式地将其设置为false!以下是我的调试输出:

{{0 0} true} 0                       # Fork 0 is available
set false                            # Fork 0 has been picked up
Philo  0  picked up fork  0          # Repsonse from Philo 0 confirming the above
{{0 0} true} 0                       # Fork 1 is available
set false                            # Fork 1 has been picked up
Philo  0  picked up fork  1          # Response from Philo 0 confirming the above
Philo  0  has both forks; eating...  # As Philo 0 has both forks, they can now eat...

{{0 0} true} 1                     **# Philo 1 checks Fork 0's availability, **which is true?**
set false                            # Philo 1 sets Fork 0's availability to false
Philo  1  picked up fork  1          # Response of ^
{{0 0} true} 1                       
set false                            
Philo  1  picked up fork  2
Philo  1  has both forks; eating...
菲洛一号不应该拿起叉子。由于Philo 1被互斥锁锁定,并且拾取的唯一两个退出条件是在可用性为false之后,因此叉不可能可用


但事实确实如此。为什么会这样?如何解决此问题?

我认为问题几乎肯定是您的
开始定义方法的签名:

func (phl *Philosopher) StartDining(forkList [9]Fork)
Go中的数组是按值传递的,所以每次调用
StartDining
,都是传递forks的副本。哲学家们在完全分开的桌子上吃饭


请尝试传入指向数组的指针。

或者更好的方法是使用切片。事实上,我可能应该为此使用切片。通过值将需要一些时间来适应!不过,谢谢!快速问题:如果数组被复制,为什么哲学家1会被互斥锁锁住?我不明白你为什么认为哲学家1被互斥锁锁住了。@Evan因为Philo 1在Philo 0释放叉之后才运行。