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关闭”。我对吗?不幸的是,我从来没有得到这个理论上的结果。
顺便说一句:我知道应该避免在一个例程中读取和写入通道。暂时不要理会。 p>
答案 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")
}