主要功能在退出前不等待频道读取吗?

时间:2019-04-24 17:21:18

标签: go goroutine channels

考虑以下尝试使用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

问题为什么会发生?

我已经知道的:

  1. 如果main转到例程完成(即使某些其他例程仍在运行),程序将退出。

  2. 如果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
}

不过,如果有人可以验证这个解释,那将是很好的:)

2 个答案:

答案 0 :(得分:4)

您在stdout中看到的与发生的情况不同。有时,mainplates接收,然后在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)`

为使其正确打印,在写入印版通道之前,您需要运行此行。