为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
梦里花落0921 2021-06-12 17:05:42
我正在用 Go 编写一个 Philosophers Dining 解决方案。我的解决方案很简单:检查两个叉子是否都可用。如果是这样,请选择两者。如果没有,请保留两者。但是,我遇到了一个奇怪的并发错误,即使在明确设置为 false 之后,fork 的可用性仍然是 true。我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()}当 Philosopher 调用 时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非常简单:它会检查是否可以获得第一个分叉。然后,它检查是否可以得到第二个分叉;如果不能,它会放下第一个叉子。如果可以,它会同时保持 5 秒钟,然后将两者都放下。出于测试目的,我将其限制为两位哲学家。但是,这不能正常工作。哲学家 0 拿起第一个叉子,然后是第二个叉子。我已经验证这些分叉的可用性已设置为 false。此时,哲学家 1 已被互斥锁锁定。但是,一旦 Philo 0 释放了互斥锁,Philo 1 就会进入该函数。此时的预期结果是PickUp()函数返回 false;由于叉子不再可用,因此无法捡起。但是,该函数不这样做;它返回 true 并允许 Philo 1 拿起叉子!更神秘的是,当 Philo 1 去拿起叉子时,叉子的可用性是true,即使 Philo 0 明确地将它们设置为 false!这是我的调试输出:Philo 1 应该永远无法拿起叉子。由于 Philo 1 被互斥锁锁定,并且只有两个退出条件PickUp是在可用性为假之后,因此分叉不可能可用。然而它是。为什么是这样?我该如何解决这个问题?
查看完整描述

1 回答

?
斯蒂芬大帝

TA贡献1827条经验 获得超8个赞

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

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

Go 中的数组是按值传递的,因此每次调用 时StartDining,都会传入分叉的副本。哲学家们在完全不同的桌子上用餐!

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


查看完整回答
反对 回复 2021-06-21
  • 1 回答
  • 0 关注
  • 264 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信