关于主要例程和同时监听同一频道的子例程的问题

问题描述 投票:3回答:1
func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c)

    ticker := time.NewTicker(time.Second)
    stop := make(chan bool)

    go func() {
        defer func() { stop <- true }()
        for {
            select {
            case <-ticker.C:
                fmt.Println("Tick")
            case <-stop:
                fmt.Println("Goroutine closing")
                return
            }
        }
    }()

    <-c
    ticker.Stop()

    stop <- true

    <-stop
    fmt.Println("Application stopped")
}

无论我运行多少次上面的代码,我都得到了相同的结果。也就是说,按下Ctrl + C后,“应用程序停止”之前始终打印“Goroutine关闭”。

我认为,理论上,“Goroutine关闭”有可能根本不打印。我对吗?不幸的是,我从未得到过这个理论结果。

顺便说一句:我知道应该避免在一个例行程序中读写一个频道。暂时忽略它。

go channel goroutine
1个回答
3
投票

在你的情况下,Goroutine closing将始终执行,它将始终在Application stopped之前打印,因为你的stop通道没有缓冲。这意味着发送将阻塞,直到收到结果。

在你的代码中,stop <- true中的main将阻塞,直到goroutine收到该值,导致该通道再次为空。然后你的主要的<-stop将阻止,直到另一个值发送到通道,这发生在你的goroutine打印Goroutine closing后返回。

如果您要以缓冲方式初始化频道

stop := make(chan bool, 1)

那么Goroutine closing可能不会被执行。要看到这一点,你可以在打印time.Sleep之后立即添加一个Tick,因为这样更容易发生这种情况(每次在睡眠期间按Ctrl + C都会发生这种情况)。

使用sync.WaitGroup等待goroutines完成是一个很好的选择,特别是如果你不得不等待多个goroutine。你也可以使用context.Context来阻止goroutines。重新编写代码以使用这两种方法可能如下所示:

func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c)

    ticker := time.NewTicker(time.Second)
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        defer func() { wg.Done() }()
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Goroutine closing")
                return
            case <-ticker.C:
                fmt.Println("Tick")
                time.Sleep(time.Second)
            }
        }
    }()

    <-c
    ticker.Stop()

    cancel()

    wg.Wait()

    fmt.Println("Application stopped")
}
© www.soinside.com 2019 - 2024. All rights reserved.