考虑以下尝试使用Go例程和通道来实现Dining Philosophers。
package main
import "fmt"
func philos(id int, left, right, plate chan bool) {
fmt.Printf("Philosopher # %d wants to eat\n", id)
<-left
<-right
plate <- true
left <- true
right <- true
fmt.Printf("Philosopher # %d finished eating\n", id)
}
func main() {
const numPhilos = 5
var forks [numPhilos]chan bool
for i := 0; i < numPhilos; i++ {
forks[i] = make(chan bool, 1)
forks[i] <- true
}
plates := make(chan bool)
for i := 0; i < numPhilos; i++ {
go philos(i, forks[(i-1+numPhilos)%numPhilos], forks[(i+numPhilos)%numPhilos], plates)
}
for i := 0; i < numPhilos; i++ {
<-plates
}
}
有时这会按预期工作,即所有哲学家都在吃东西,例如:
Philosopher # 4 wants to eat
Philosopher # 3 wants to eat
Philosopher # 2 wants to eat
Philosopher # 1 wants to eat
Philosopher # 4 finished eating
Philosopher # 3 finished eating
Philosopher # 2 finished eating
Philosopher # 1 finished eating
Philosopher # 0 wants to eat
Philosopher # 0 finished eating
但是,有时会错过一个(或多个)哲学家(例如,#0哲学家在以下情况下没有进食):
Philosopher # 4 wants to eat
Philosopher # 1 wants to eat
Philosopher # 3 wants to eat
Philosopher # 2 wants to eat
Philosopher # 4 finished eating
Philosopher # 0 wants to eat
Philosopher # 2 finished eating
Philosopher # 1 finished eating
Philosopher # 3 finished eating
问题:为什么会发生?
我已经知道的:
如果main
转到例程完成(即使某些其他例程仍在运行),程序将退出。
如果go例程尝试从某个通道读取并且该通道为空(即以前没有人向其写入数据),它将被阻塞。
现在,main
尝试从通道plates
读取5次,因此,它应该在philos
例程运行五次之前终止。但是似乎它仍然可以在此之前终止。我想念什么吗? (似乎plates
仅被读取了4次。)
编辑:好的,再多考虑一下,我得出的结论是,也许philos
例程总是运行5次,但是它可以被中断< strong>之前,有时间打印出这位哲学家吃过的东西。确实,如果我按如下所示更改顺序,那么它似乎总是可以正常工作:
func philos(id int, left, right, plate chan bool) {
fmt.Printf("Philosopher # %d wants to eat\n", id)
<-left
<-right
left <- true
right <- true
fmt.Printf("Philosopher # %d finished eating\n", id)
plate <- true
}
不过,如果有人可以验证这个解释,那将是很好的:)
答案 0 :(得分:4)
您在stdout中看到的与发生的情况不同。有时,main
从plates
接收,然后在print语句发生之前返回。所以:
plate <- true
left <- true // On this line or on
right <- true // this line, main receives from plate and then returns before
fmt.Printf("Philosopher # %d finished eating\n", id) // this line executes
因为并发不是确定性的,所以并非每次都不会发生。有时打印会在main
返回之前进行,有时不会。这并不意味着频道读取没有发生。
答案 1 :(得分:1)
实际上,通道被读取了5次,但是由于main函数仅在第5次等待读取通道,因此它在philos函数到达此行之前退出:
fmt.Printf("Philosopher # %d finished eating\n", id)`
为使其正确打印,在写入印版通道之前,您需要运行此行。