Golang停止Ticker的那些坑


原文链接: Golang停止Ticker的那些坑

golang-notes/timer.md at master · cch123/golang-notes
Golang如何正确的停止Ticker - 我的天空 我的梦 - CSDN博客

timer.NewTimer()会启动一个新的Timer实例,并开始计时。 我们启动一个新的goroutine,来以阻塞的方式从Timer的C这个channel中,等待接收一个值,这个值是到期的时间。并打印”Timer has expired.”

到现在看起来似乎没什么问题,但是当我们执行timer.Stop()之后,3秒钟过去了,程序却没有打印那句话。说明执行timer.Stop()之后,Timer自带的channel并没有关闭,而且这个Timer已经从runtime中删除了,所以这个Timer永远不会到期。

这会导致程序逻辑错误,或者更严重的导致goroutine和内存泄露。解决的办法是,使用timer.Reset()代替timer.Stop()来停止定时器。

Golang可以利用time包的Ticker实现定时器的作用,最近使用Ticker时,发现调用Ticker的Stop方法无法正确的停止Ticker,协程会阻塞在等待Ticker的C通道处,精简后的代码如下:

func UseTickerWrong() *time.Ticker {
	ticker := time.NewTicker(5 * time.Second)
	go func(ticker *time.Ticker) {
		for range ticker.C {
			fmt.Println("Ticker1....")  // 死循环
		}
		
		fmt.Println("Ticker1 Stop")
	}(ticker)
	
	return ticker
}

大家都需要注意的是:不要对 Ticker.C 使用 range,因为在调用 Ticker.Stop 的时候并不会 close 该 channel,所以这里相当于一个死循环。

函数中我们创建一个5s的定时器,然后启动协程,在协程中我们读取Ticker的C通道,当定时时间到达时,该通道就会读到数据。我们在主函数中调用

func main() {

ticker1 := UseTickerWrong()
time.Sleep(20 * time.Second)
ticker1.Stop()

}
输出结果为:

Ticker1....
Ticker1....
Ticker1....
Ticker1....

并没有最后的Ticker1 Stop,查看Ticker的Stop方法的说明会发现:

// Stop turns off a ticker. After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.

翻一下就是Stop会停止Ticker,停止后,Ticker不会再被发送,但是Stop不会关闭通道,防止读取通道发生错误。

Golang中从已经关闭的通道读取数据会发生错误,Ticker的通道不关闭,防止我们在不必要的时候读取了已经关闭的通道。那么,到底如何科学的停止ticker呢?可以看看下面的函数

func UserTicker() chan bool {
	ticker := time.NewTicker(5 * time.Second)
	
	stopChan := make(chan bool)
	go func(ticker *time.Ticker) {
		defer ticker.Stop()
		
		for {
			select {
				case <-ticker.C:
					fmt.Println("Ticker2....")
				case stop := <-stopChan:
					if stop {
						fmt.Println("Ticker2 Stop")
						return
					}
			}
		}
	}(ticker)
	
	return stopChan
}

我们通过select读取两个通道,当stop通道读到true的时候,函数返回,由于使用了defer,会调用Ticker的Stop方法,之后从协程返回,主函数调用如下所示:

func main() {

ch := UserTicker()
time.Sleep(20 * time.Second)
ch <- true
close(ch)

}
输出如下所示

Ticker2....
Ticker2....
Ticker2....
Ticker2....
Ticker2 Stop

可以看到,我们可以正常退出协程了

原文:https://blog.csdn.net/yjp19871013/article/details/82048944

`