在具有 select case 和 default 的 Goroutines 中,一旦通道关闭,default 不应该被执行

问题描述 投票:0回答:1

我试图理解默认情况下选择块中关闭通道的行为,但对以下输出感到困惑。 这里调用 50 个 goroutine 并关闭完成通道。

func testClosedChannelBehavior() {
    const n = 50
    finish := make(chan bool)
    var done sync.WaitGroup
    for i := 0; i < n; i++ {
        done.Add(1)
        go func(x int) {
            select {
            case <-time.After(1 * time.Hour):
            case <-finish:
                fmt.Printf("received finish %d\n", x)
            default:
            fmt.Printf("I didnt wait %d\n", x)
            }
            done.Done()
        }(i)
    }
    t0 := time.Now()
    close(finish) 
    fmt.Println("finish closed")
    done.Wait() 
    fmt.Printf("Waited %v for %d goroutines to stop\n",     time.Since(t0), n)
}

我预计一旦任何 Goroutine 打印出“received finish”,默认情况就不应该被任何其他 Goroutine 执行,即“I did not wait”不应该被打印。 但输出并不一致。有时它的行为符合预期,但在多次运行时,我可以看到意外的输出,如下所示:

I didnt wait 0
received finish 7
finish closed
received finish 13
received finish 10
received finish 32
received finish 5
received finish 14
received finish 33
received finish 42
received finish 11
received finish 4
received finish 23
received finish 44
received finish 49
received finish 15
received finish 24
received finish 31
received finish 16
received finish 40
received finish 41
received finish 6
received finish 26
I didnt wait 1
received finish 19
received finish 8
received finish 43
received finish 29
received finish 20
received finish 46
received finish 12
received finish 36
received finish 47
received finish 37
received finish 35
received finish 30
received finish 39
received finish 22
received finish 28
I didnt wait 2
received finish 17
received finish 45
I didnt wait 9
received finish 48
received finish 34
I didnt wait 3
received finish 25
received finish 38
received finish 27
received finish 18
received finish 21
Waited 394.999µs for 50 goroutines to stop

我正在浏览这个链接,期望关闭(完成)会向其他仍在等待的人发出信号,让他们表现得相似。

go
1个回答
3
投票

对 fmt.Printf 的调用涉及系统调用。 系统调用会自动导致该 goroutine 被重新调度,因为它必须等待操作系统完成该系统调用。 这意味着其中一些 goroutine 很可能运行 select 语句并选择默认情况,但还没有打印到控制台。

编辑: 另外,如果您在具有多个线程的系统上运行此程序,则默认情况下,Go 运行时将运行多个 Go 例程并行(与操作系统线程数匹配),这意味着其中一些 goroutine可以在通道关闭的同时执行,并在主 goroutine 中发生通道关闭之前到达 select 语句。

如果添加同步通道以确保通道关闭操作发生在任何 goroutines 中发生select之前

,它会按预期工作:

https://play.golang.org/p/XtUYaihKgRT

func testClosedChannelBehavior() { const n = 50 finish := make(chan bool) proceed := make(chan struct{}) var done sync.WaitGroup for i := 0; i < n; i++ { done.Add(1) go func(x int) { <-proceed select { case <-time.After(1 * time.Hour): case <-finish: fmt.Printf("received finish %d\n", x) default: fmt.Printf("I didnt wait %d\n", x) } done.Done() }(i) } t0 := time.Now() close(finish) fmt.Println("finish closed") close(proceed) done.Wait() fmt.Printf("Waited %v for %d goroutines to stop\n", time.Since(t0), n) }
    
© www.soinside.com 2019 - 2024. All rights reserved.