本文代码部分基于dive-to-gosync-workshop的代码
Golang 的NewTimer方法调用后,生成的timer会放入最小堆,一个后台goroutine会扫描这个堆,将到时的timer进行回调和channel(下面代码的 c := make(chan Time,1) )写入
// NewTimer creates a new Timer that will send // the current time on its channel after at least duration d. func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&t.r) return t }
而golang的timer的Stop方法, 是只负责把timer从堆里移除,不负责close 上面的channel(为什么不close channel?目前看只是为了超时时, 底层代码处理简单不crash。其实golang官方是可以做到的超时时正确处理channel的),这样就买下了一些坑。
下面的代码示范了这些坑和处理方法,其中 wrongResetAfterFired(..) 说明了超时后的channel被写入,如果没有被主动的正确接收,会导致的reset后的timer依然从channel拿到上一次的通道数据。
而wrongStopMore(...) 说明,如果channel没有被写入,也不要直接去等待,会导致deadlock
package main import ( "fmt" "log" "time" ) // [jz] 关于timer一个比较重要的点是,newtimer后,timer会放入最小堆,然后有一个goroutine来扫描,到期的进行回调和channel写入 // stop只负责将timer从堆删除,不负责close channel func main() { log.Println("?? resetBeforeFired") resetBeforeFired() fmt.Println() log.Println("? wrongResetAfterFired") wrongResetAfterFired() fmt.Println() log.Println("?? correctResetAfterFired") correctResetAfterFired() fmt.Println() log.Println("?? stop n times") stopMore() fmt.Println() log.Println("? stop n times but with drain") wrongStopMore() fmt.Println() log.Println("? too many receiving") wrongReceiveMore() } func resetBeforeFired() { timer := time.NewTimer(5 * time.Second) b := timer.Stop() log.Printf("stop: %t", b) timer.Reset(1 * time.Second) t := <-timer.C log.Printf("fired at %s", t.String()) } func wrongResetAfterFired() { timer := time.NewTimer(5 * time.Millisecond) time.Sleep(time.Second) // sleep 1s能保证上面的timer 超时,channel被写入 b := timer.Stop() log.Printf("stop: %t", b) tt := timer.Reset(10 * time.Second) fmt.Println(tt) // 此时拿到的是第一个timer(5毫秒那个)的timeout的channel值 t := <-timer.C log.Printf("fired at %s", t.String()) } func correctResetAfterFired() { timer := time.NewTimer(5 * time.Millisecond) time.Sleep(time.Second) b := timer.Stop() log.Printf("stop: %t", b) // 如果stop的时候发现已经超时,此时要把channel里的写入读出,免得后面reset时读出之前的channel里的值 if !b { t := <-timer.C fmt.Println(t.String()) } log.Printf("reset") timer.Reset(10 * time.Second) t := <-timer.C log.Printf("fired at %s", t.String()) } func wrongReceiveMore() { timer := time.NewTimer(5 * time.Millisecond) t := <-timer.C log.Printf("fired at %s", t.String()) t = <-timer.C log.Printf("receive again at %s", t.String()) } func stopMore() { timer := time.NewTimer(5 * time.Millisecond) b := timer.Stop() log.Printf("stop: %t", b) time.Sleep(time.Second) b = timer.Stop() log.Printf("stop more: %t", b) } /* newtimer后,timer会放入最小堆,然后有一个goroutine来扫描,到期的进行回调和channel写入 stop只负责将timer从堆删除,不负责close channel */ func wrongStopMore() { timer := time.NewTimer(5 * time.Millisecond) b := timer.Stop() log.Printf("stop: %t", b) time.Sleep(time.Second) b = timer.Stop() if !b { // 可以考虑这样解决:if !b && len(timer.C) > 0 // 之所以出问题,是因为,第一次Stop调用,发生在timer超时前,此时timer已经从堆删除,而timer本身没有超时,所以不需要发送channel // 此时你去等待timer.C是不会有结果的 // 比如你在第一个timer.Stop前sleep 1s,让timer超时,channel会被写入,此时等待timer .C就不会有问题 <-timer.C } time.Sleep(1 * time.Second) log.Printf("stop more: %t", b) }
原文:https://www.cnblogs.com/jiangz222/p/11622495.html