這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
我們總是會使用Timer去執行一些定時任務,最近在Go語言的定時器使用上面不小心踩到一點問題,這裡記錄一下。
go demo(input)func demo(input chan interface{}) { for { select { case msg <- input: println(msg) case <-time.After(time.Second * 5): println("5s timer") case <-time.After(time.Second * 10): println("10s timer") } }}
寫出上面這段程式的目的是從 input channel 持續接收訊息加以處理,同時希望每過5秒鐘和每過10秒鐘就分別執行一個定時任務。但是當你執行這段程式的時候,只要 input channel 中的訊息來得足夠快,永不間斷,你會發現啟動的兩個定時任務都永遠不會執行;即使沒有訊息到來,第二個10s的定時器也是永遠不會執行的。原因就是 select 每次執行都會重新執行 case 條件陳述式,並重新註冊到 select 中,因此這兩個定時任務在每次執行 select 的時候,都是啟動了一個新的從頭開始計時的 Timer 對象,所以這兩個定時任務永遠不會執行。
其實,
select {case msg <- input:case <-time.After(time.Second)}
這個利用 time.After() 啟動 Timer 的編程手法主要是用來解決 channel 操作的 Timeout 問題,而不是執行定時任務。Go 語言採用這種方式來實現 channel 的 Timeout 究竟怎麼樣?這個話題暫時不在這裡分析。
如何正確使用 Timer 來完成上面提到的定時任務?
func demo(input chan interface{}) { t1 := time.NewTimer(time.Second * 5) t2 := time.NewTimer(time.Second * 10) for { select { case msg <- input: println(msg) case <-t1.C: println("5s timer") t1.Reset(time.Second * 5) case <-t2.C: println("10s timer") t2.Reset(time.Second * 10) } }}
改正後的程式,原理上是自訂兩個全域的 Timer,每次執行 select 都重複使用這兩個 Timer,而不是每次都產生全新的。這樣才可以真正做到在接收訊息的同時,還能夠定時的執行相應的任務。