为什么将同时写入的布尔值设置为false后仍然为true?
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?
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.