英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论