为什么在被设置为false之后,同时编写的布尔值仍然为true?

huangapple go评论81阅读模式
英文:

Why is the concurrently-written boolean value still true after being set to false?

问题

我正在用Go语言编写一个哲学家就餐问题的解决方案。我的解决方案很简单:检查两个叉子是否可用。如果可用,就同时拿起两个叉子。如果不可用,就都放下。

然而,我遇到了一个奇怪的并发错误,即使明确将叉子的可用性设置为false,叉子的可用性仍然为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()
}

当一个哲学家调用PickUp()函数时,程序会等待互斥锁;如果此时叉子可用,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被互斥锁锁住。然而,一旦哲学家0释放了互斥锁,哲学家1就进入了函数。

在这一点上,PickUp()函数应该返回false;因为叉子不再可用,所以不能拿起。然而,函数没有这样做;它返回true,并允许哲学家1拿起叉子!

更加神秘的是,当哲学家1去拿起叉子时,叉子的可用性是true,即使哲学家0明确将其设置为false!这是我的调试输出:

{{0 0} true} 0                       # 叉子0可用
set false                            # 叉子0已被拿起
Philo  0  picked up fork  0          # 哲学家0的回应,确认上述情况
{{0 0} true} 0                       # 叉子1可用
set false                            # 叉子1已被拿起
Philo  0  picked up fork  1          # 哲学家0的回应,确认上述情况
Philo  0  has both forks; eating...  # 由于哲学家0拿到了两个叉子,他们现在可以吃饭了...

{{0 0} true} 1                     **# 哲学家1检查叉子0的可用性,结果是true?**
set false                            # 哲学家1将叉子0的可用性设置为false
Philo  1  picked up fork  1          # 上述操作的回应
{{0 0} true} 1                        # 叉子1仍然可用
set false                             # 叉子1已被拿起
Philo  1  picked up fork  2
Philo  1  has both forks; eating...

哲学家1不应该能够拿起叉子。由于哲学家1被互斥锁锁住,而PickUp函数的唯一两个退出条件是在可用性为false之后,叉子不可能可用。

然而它确实可用。为什么会这样?我该如何解决这个问题?

英文:

I'm writing a Philosophers Dining solution in Go. My solution is simple: check if both forks are available. If so, pick both up. If not, leave both be.

However, I'm running into a weird concurrency error, whereby a fork's availability is still true even after explicitly being set to false. My Fork is declared like so:

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()
}

When a Philosopher calls PickUp(), the program will wait for the mutex lock; if, at that point, the fork is available, the Fork sets its availability boolean to false and returns true to indicate the operation was successful.

The Philosopher is written like so:

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()
			}
		}
	}
}

(Note: The getLeftSpace() function has been excluded because its implementation is irrelevant; it simply gets the index of the leftward space.)

The implementation of the Philosopher is very simple: it checks to see if it can get the first fork. Then, it checks to see if it can get the second fork; if it can't, it puts down the first fork. If it can, it holds both for 5 seconds, then puts both down. I've limited this to two philosophers for testing purposes.


However, this isn't working properly. Philosopher 0 picks up the first fork, and then the second fork. I have verified that the availability of these forks have been set to false. By this point, Philosopher 1 is locked by the mutex. However, once Philo 0 has released the mutex, Philo 1 enters the function.

The expected result at this point would be for the PickUp() function to return false; as the fork is no longer available, it cannot be picked up. However, the function does not do this; it returns true and allows Philo 1 to pick up the fork!

Even more mysterious, by the time Philo 1 goes to pick up the fork, the availability of the fork is true, even though Philo 0 explicitly set them to false! Here is my debug output:

{{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 should never have been able to pick up the fork. Since Philo 1 was locked by a mutex, and the only two exit conditions for PickUp are after the availability is false, the fork could not possibly have been available.

Yet it is. Why is this? How can I resolve this problem?

答案1

得分: 2

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

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

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

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

英文:

I think the problem is almost certainly the signature of your StartDining method:

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

Arrays in Go are passed by value, so every time you call StartDining, you're passing in copies of the forks. The philosophers are dining at completely separate tables!

Try passing in a pointer to an array instead.

huangapple
  • 本文由 发表于 2013年8月4日 13:55:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/18040143.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定