为什么在设置为false后,并发写入的布尔值仍为true?

时间:2013-08-04 05:55:12

标签: concurrency go mutex dining-philosopher

我正在Go中写一个Philosophers Dining解决方案。我的解决方案很简单:检查两个叉子是否都可用。如果是这样,请同时选择。如果没有,请将两者都留下来。

但是,我遇到了一个奇怪的并发错误,即使在明确设置为false之后,fork的可用性仍然是正确的。我的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()
}

当哲学家调用PickUp()时,程序将等待互斥锁;如果在那时,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()函数已被排除,因为它的实现无关紧要;它只是获取向左空格的索引。)

Philosopher的实现非常简单:它检查它是否可以获得第一个fork。然后,它检查它是否可以得到第二个分叉;如果它不能,它放下第一个分叉。如果可以的话,它会持续5秒,然后将两者都放下。为了测试目的,我把它限制在两位哲学家身上。


但是,这不能正常工作。哲学家0拿起第一个叉子,然后是第二个叉子。我已经确认这些分支的可用性已设置为false。到此为止,哲学家1被互斥锁锁定。但是,一旦Philo 0发布了互斥锁,Philo 1就会进入该功能。

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

更为神秘的是,当Philo 1去拿叉子时,叉子的可用性是true,即使Philo 0明确地将它们设置为假!这是我的调试输出:

{{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...

菲洛1应该永远不能拿起前叉。由于Philo 1被互斥锁锁定,PickUp的唯一两个退出条件是 后可用性为false,因此无法使用fork。

但事实确实如此。为什么是这样?我该如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

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

func (phl *Philosopher) StartDining(forkList [9]Fork)

Go中的数组按值传递,因此每次调用StartDining时,都会传递分片的副本。哲学家们正在完全独立的餐桌上用餐!

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