Go Ticker實現原理剖析(輕鬆掌握Ticker實現原理)

前言

本節咱們從Ticker數據結構入手,結合源碼分析Ticker的實現原理。git

實際上,Ticker與以前講的Timer幾乎徹底相同,不管數據結構和內部實現機制都相同,惟一不一樣的是建立方式。github

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只有兩個成員:ui

  • C: 管道,上層應用跟據此管道接收事件;
  • r: runtime定時器,該定時器即系統管理的定時器,對上層應用不可見;

這裏應該按照層次來理解Ticker數據結構,Ticker.C即面向Ticker用戶的,Ticker.r是面向底層的定時器實現。code

runtimeTimer

runtimeTimer也與Timer同樣,這裏再也不贅述。協程

實現原理

建立Ticker

咱們來看建立Ticker的實現,很是簡單:blog

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項目,求星~ 點我即達

相關文章
相關標籤/搜索