本文代碼部分基於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)
}
