使用newTicker的情境
在Golang,通常我們使用”time” Package 中的newTicker(d duration)來生成一個固定的計時器,每當經過d時間,就會引發某個函數或執行某些動作,經典的用法如下:
func somegoroutine(ctx context.Context, duration time.Duration) { ticker := newTicker(duration) defer ticker.Stop() for { select{ case <- ticker.C: //每過duration 時間就執行一次 doSomething() doSomething() case <-ctx.Done(): //可由Cancel()函數由外部終止此函數,以避免無窮迴圈 return } }
這時有一個小問題來了,如果假設doSomething()執行了很久,超過duration的時間,那程式會不會被Queue越來越多等待執行的doSomething呢? 小提示: ticker.C 為一個 timer 的Channel 且通道buffer數為1。(換一個講法:正常的Channel會造成阻塞,並等待ticker.C被走取,因此我們擔心阻塞造成待執行的ticker.C越來越多)
先回答結果,不會,ticker時間到了但ticker.C沒被清空(因為還在執行上一個doSomething())的情況下,time時間到了仍會產生tick,但因為C的buffer滿了,會丟棄此新產生的tick,繼續等下一個duration的時間到了再產生新的tick,如此重複,直到ticker.C空出位置來。網路上有更多原始碼分析,這邊就不贅述了,用以下的小測試可以看到看到如上所說的結果:
小測試
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(1 * time.Second) for { select { case t := <-ticker.C: //C為一各time type的Channel ,可將其值取出得到時間 time.Sleep(3 * time.Second) //模擬超過duration時間的程序 fmt.Println("時間:", t) } } }
執行結果如下:
時間: 2023-03-11 17:58:48.626068 +0800 CST m=+1.005058301
時間: 2023-03-11 17:58:49.6377993 +0800 CST m=+2.016789601
時間: 2023-03-11 17:58:52.6287877 +0800 CST m=+5.007778001exit status 3221225786
可發現第一次執行很正常,C為48秒時,此時C通道清空,過了一秒鐘,塞入49秒的tick到C,同時間系統正在執行time.Sleep 3秒,因此此49秒的tick在3秒後才被取出,此中間3秒 C Chan是阻塞的,因此塞不進50秒、51秒的 tick,直到52秒的tick時,因為前面的time.Sleep執行完成了,從C中取出了49秒的tick並空出了通道,因此又將52秒的tick塞入C中,等待下一次被取出。