Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top