【问题标题】:Go ticker example doesn't select the 'done' case?Go ticker 示例不选择“完成”案例?
【发布时间】:2019-10-06 22:34:04
【问题描述】:

我已将此示例 https://gobyexample.com/tickers 改编为以下脚本:

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)

    go func() {
        for {
            select {
            case <-done:
                fmt.Println("Received 'done'")
                return
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()

    time.Sleep(1600 * time.Millisecond)
    // ticker.Stop()
    done <- true
    // fmt.Println("Ticker stopped.")
}

与引用示例的两个不同之处是我注释掉了ticker.Stop() 行并在case &lt;-done 块中添加了fmt.Println("Received 'done'") 行。如果我运行它,我会观察到以下输出:

> go run tickers.go
Tick at 2019-10-06 15:25:50.576798 -0700 PDT m=+0.504913907
Tick at 2019-10-06 15:25:51.074993 -0700 PDT m=+1.003102855
Tick at 2019-10-06 15:25:51.576418 -0700 PDT m=+1.504521538

我的问题:为什么它不将Received 'done' 打印到终端?

奇怪的是,如果我在 Ticker stopped Println 语句中发表评论,我确实也会看到 Received 'done'

> go run tickers.go
Tick at 2019-10-06 15:27:30.735163 -0700 PDT m=+0.504666656
Tick at 2019-10-06 15:27:31.234076 -0700 PDT m=+1.003573649
Tick at 2019-10-06 15:27:31.735342 -0700 PDT m=+1.504833296
Ticker stopped.
Received 'done'

我记得,Goroutine 中的代码可以假设是同步运行的,所以我很困惑,在前一种情况下我看不到 Println 语句的效果,因为它发生在 Goroutine 返回之前.有人可以解释一下吗?

【问题讨论】:

    标签: go concurrency goroutine


    【解决方案1】:

    done &lt;- true发生时,main函数直接返回。

    你可以再添加一个 time.Sleep() 来看看发生了什么。

        time.Sleep(1600 * time.Millisecond)
        // ticker.Stop()
        done <- true
        // fmt.Println("Ticker stopped.")
        time.Sleep(1600 * time.Millisecond)
    

    【讨论】:

      【解决方案2】:

      ...为什么它不将 Received 'done' 打印到终端?

      它确实——或者更确切地说,它尝试

      当主 goroutine(称为包 mainmain)返回时(或者在此处未发生的各种情况下更快),Go 程序退出。你的main 在调用time.Sleep() 之后返回,然后在done 上发送true

      同时,for 循环中的 goroutine 在true 值到达done 通道时唤醒。这发生在主 goroutine 发送之后,之后主 goroutine 正在退出过程中。

      如果在这个退出的过程中,主协程花费了足够长的时间,匿名协程将有时间打印Received 'done'。如果在这个退出过程中,主 goroutine 足够快,匿名 goroutine 永远不会完成,或者甚至永远不会启动,打印任何东西,你什么也看不到。 (实际的输出是由一个底层系统调用完成的,所以你要么得到它的全部,要么不得到它。)

      您可以通过多种机制确保您分拆的 goroutine 在您的 main 退出之前完成,但最简单的可能是使用 sync.WaitGroup,因为它就是为此而设计的。创建一个等待组,将其计数器设置为 1(将其初始零加 1),然后在退出匿名 goroutine 的途中调用 Done 函数。让主 goroutine 等待它。

      Go Playground example

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-07
        相关资源
        最近更新 更多