有关主例程和子例程同时在同一频道上收听的问题

时间:2018-09-03 09:47:21

标签: go channel goroutine

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关闭”。我对吗?不幸的是,我从来没有得到这个理论上的结果。

顺便说一句:我知道应该避免在一个例程中读取和写入通道。暂时不要理会。

1 个答案:

答案 0 :(得分:3)

对于您而言,Goroutine closing将始终执行,并且始终在Application stopped之前打印,因为您的stop通道未缓冲。这意味着发送将一直阻塞,直到接收到结果为止。

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

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

stop := make(chan bool, 1)

然后可能不会执行Goroutine closing。为此,您可以在打印Tick之后立即添加一个time.Sleep,因为这会使这种情况更容易发生(每次您按 Ctrl + C < / kbd>)。

使用sync.WaitGroup等待goroutine完成是一个很好的选择,尤其是当您必须等待多个goroutine时。您也可以使用context.Context停止goroutine。重新编写代码以使用这两种方法可能看起来像这样:

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")
}