行情自动收录器示例未选择“完成”情况?

时间:2019-10-06 22:34:04

标签: go concurrency goroutine

我已将示例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()行,并在fmt.Println("Received 'done'")块中添加了case <-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返回。有人可以解释吗?

2 个答案:

答案 0 :(得分:2)

发生done <- true时,主函数直接返回。

您可以添加另一个时间。Sleep()看看发生了什么。

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

答案 1 :(得分:1)

  

...为什么不将“接收到的”“完成”打印到终端?

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

当主goroutine(称为程序包main的{​​main)返回时(或者在各种情况下不早于此),Go程序退出。您的main会在调用time.Sleep()之后在true上发送done之后返回。

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

如果在退出过程中主goroutine花费了足够长的时间,则匿名goroutine将有时间打印Received 'done'。如果在退出过程中主goroutine足够快,则匿名goroutine永远不会完成,甚至可能永远不会开始,打印任何内容,而您什么也看不到。 (实际输出是通过单个基础系统调用完成的,因此您可以全部获得,也可以全部不获得。)

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

请参见Go Playground example