Go Ticker實現原理剖析(輕鬆掌握Ticker實現原理)
前言
本節我們從Ticker資料結構入手,結合原始碼分析Ticker的實現原理。
實際上,Ticker與之前講的Timer幾乎完全相同,無論資料結構和內部實現機制都相同,唯一不同的是建立方式。
Timer建立時,不指定事件觸發週期,事件觸發後Timer自動銷燬。而Ticker建立時會指定一個事件觸發週期,事件會按照這個週期觸發,如果不顯式停止,定時器永不停止。
資料結構
Ticker
Ticker資料結構與Timer除名字不同外完全一樣。
原始碼包src/time/tick.go:Ticker
定義了其資料結構:
type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer }
Ticker只有兩個成員:
- C: 管道,上層應用跟據此管道接收事件;
- r: runtime定時器,該定時器即系統管理的定時器,對上層應用不可見;
這裡應該按照層次來理解Ticker資料結構,Ticker.C即面向Ticker使用者的,Ticker.r是面向底層的定時器實現。
runtimeTimer
runtimeTimer也與Timer一樣,這裡不再贅述。
實現原理
建立Ticker
我們來看建立Ticker的實現,非常簡單:
func NewTicker(d Duration) *Ticker { if d <= 0 { panic(errors.New("non-positive interval for NewTicker")) } // Give the channel a 1-element time buffer. // If the client falls behind while reading, we drop ticks // on the floor until the client catches up. c := make(chan Time, 1) t := &Ticker{ C: c, r: runtimeTimer{ when: when(d), period: int64(d), // Ticker跟Timer的重要區就是提供了period這個引數,據此決定timer是一次性的,還是週期性的 f: sendTime, arg: c, }, } startTimer(&t.r) return t }
NewTicker()只是構造了一個Ticker,然後把Ticker.r通過startTimer()交給系統協程維護。
其中period為事件觸發的週期。
其中sendTime()方法便是定時器觸發時的動作:
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
sendTime接收一個管道作為引數,其主要任務是向管道中寫入當前時間。
建立Ticker時生成的管道含有一個緩衝區(make(chan Time, 1)
),但是Ticker觸發的事件確是週期性的,如果管道中的資料沒有被取走,那麼sendTime()也不會阻塞,而是直接退出,帶來的後果是本次事件會丟失。
綜上,建立一個Ticker示意圖如下:
停止Ticker
停止Ticker,只是簡單的把Ticker從系統協程中移除。函式主要實現如下:
func (t *Ticker) Stop() {
stopTimer(&t.r)
}
stopTicker()即通知系統協程把該Ticker移除,即不再監控。系統協程只是移除Ticker並不會關閉管道,以避免使用者協程讀取錯誤。
與Timer不同的是,Ticker停止時沒有返回值,即不需要關注返回值,實際上返回值也沒啥用途。
綜上,停止一個Ticker示意圖如下:
Ticker沒有重置介面,也即Ticker建立後不能通過重置修改週期。
需要格外注意的是Ticker用完後必須主動停止,否則會產生資源洩露,會持續消耗CPU資源。
總結
- NewTicker()建立一個新的Ticker交給系統協程監控;
- Stop()通知系統協程刪除指定的Ticker;
贈人玫瑰手留餘香,如果覺得不錯請給個贊~
本篇文章已歸檔到GitHub專案,求星~ 點我即