這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Golang的Timer類,是一個普遍意義上的定時器,它有著普通定時器的一些特性,例如:
- 給定一個到期時間,和一個回呼函數,到期後會調用回呼函數
- 重設定時器的逾時時間
- 停止定時器
Golang的Timer在源碼中,實現的方式是以一個小頂堆來維護所有的Timer集合。接著啟動一個獨立的goroutine,迴圈從小頂堆中的檢測最近一個到期的Timer的到期時間,接著它睡眠到最近一個定時器到期的時間。最後會執行開始時設定的回呼函數。Timer到期之後,會被Golang的runtime從小項堆中刪除,並等待GC回收資源。
下面給出實際的代碼:
package mainimport ( "time" "fmt")func main() { timer := time.NewTimer(3 * time.Second) go func() { <-timer.C fmt.Println("Timer has expired.") }() timer.Stop() time.Sleep(60 * time.Second)}
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()來停止定時器。
package mainimport ( "time" "fmt")func main() { timer := time.NewTimer(3 * time.Second) go func() { <-timer.C fmt.Println("Timer has expired.") }() //timer.Stop() timer.Reset(0 * time.Second) time.Sleep(60 * time.Second)}
這樣做就相當於給Timer一個0秒的逾時時間,讓Timer立刻到期。