## Timer實現原理
每個Go應用程序都有一個協程專門負責管理所有的Timer,這個協程負責監控Timer是否過期,過期后執行一個預定義的動作,這個動作對于Timer而言就是發送當前時間到管道中
源碼包`src/time/sleep.go:Timer`定義了其數據結構:
~~~go
type Timer struct {
C <-chan Time
r runtimeTimer
}
~~~
Timer只有兩個成員:
* C: 管道,上層應用根據此管道接收事件;
* r: runtime定時器,該定時器即系統管理的定時器,對上層應用不可見;
這里應該按照層次來理解Timer數據結構,Timer.C即面向Timer用戶的,Timer.r是面向底層的定時器實現
系統協程把runtimeTimer存放在數組中,并按照`when`字段對所有的runtimeTimer進行堆排序,定時器觸發時執行runtimeTimer中的預定義函數`f`,即完成了一次定時任務
### runtimeTimer
前面我們說過,創建一個Timer實質上是把一個定時任務交給專門的協程進行監控,這個任務的載體便是`runtimeTimer`,簡單的講,每創建一個Timer意味著創建一個runtimeTimer變量,然后把它交給系統進行監控。我們通過設置runtimeTimer過期后的行為來達到定時的目的。
源碼包`src/time/sleep.go:runtimeTimer`定義了其數據結構:
~~~go
type runtimeTimer struct {
tb uintptr // 存儲當前定時器的數組地址
i int // 存儲當前定時器的數組下標
when int64 // 當前定時器觸發時間
period int64 // 當前定時器周期觸發間隔
f func(interface{}, uintptr) // 定時器觸發時執行的函數
arg interface{} // 定時器觸發時執行函數傳遞的參數一
seq uintptr // 定時器觸發時執行函數傳遞的參數二(該參數只在網絡收發場景下使用)
}
~~~
其成員如下:
* tb: 系統底層存儲runtimeTimer的數組地址;
* i: 當前runtimeTimer在tb數組中的下標;
* when: 定時器觸發事件的時間;
* period: 定時器周期性觸發間隔(對于Timer來說,此值恒為0);
* f: 定時器觸發時執行的回調函數,回調函數接收兩個參數;
* arg: 定時器觸發時執行回調函數的參數一;
* seq: 定時器觸發時執行回調函數的參數二(Timer并不使用該參數);
### 創建Timer
我們來看創建Timer的實現,非常簡單:
~~~go
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) // 創建一個管道
t := &Timer{ // 構造Timer數據結構
C: c, // 新創建的管道
r: runtimeTimer{
when: when(d), // 觸發時間
f: sendTime, // 觸發后執行函數sendTime
arg: c, // 觸發后執行函數sendTime時附帶的參數
},
}
startTimer(&t.r) // 此處啟動定時器,只是把runtimeTimer放到系統協程的堆中,由系統協程維護
return t
}
~~~
NewTimer()只是構造了一個Timer,然后把Timer.r通過startTimer()交給系統協程維護。
其中when()方法是計算下一次定時器觸發的絕對時間,即當前時間+NewTimer()參數d。
其中sendTime()方法便是定時器觸發時的動作:
~~~go
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
~~~
sendTime接收一個管道作為參數,其主要任務是向管道中寫入當前時間。
創建Timer時生成的管道含有一個緩沖區(`make(chan Time, 1)`),所以Timer觸發時向管道寫入時間永遠不會阻塞,sendTime寫完即退出。
之所以sendTime()使用select并搭配一個空的default分支,是因為后面所要講的Ticker也復用sendTime(),Ticker觸發時也會向管道中寫入時間,但無法保證之前的數據已被取走,所以使用select并搭配一個空的default分支,確保sendTime()不會阻塞,Ticker觸發時,如果管道中還有值,則本次不再向管道中寫入時間,本次觸發的事件直接丟棄。
`startTimer(&t.r)`的具體實現在runtime包,其主要作用是把runtimeTimer寫入到系統協程的數組中,并啟動系統協程(如果系統協程還未開始運行的話)。更詳細的內容,待后面講解系統協程時再介紹。
綜上,創建一個Timer示意圖如下:

### 停止Timer
停止Timer,只是簡單的把Timer從系統協程中移除。函數主要實現如下:
~~~go
func (t *Timer) Stop() bool {
return stopTimer(&t.r)
}
~~~
stopTimer()即通知系統協程把該Timer移除,即不再監控。系統協程只是移除Timer并不會關閉管道,以避免用戶協程讀取錯誤。
系統協程監控Timer是否需要觸發,Timer觸發后,系統協程會刪除該Timer。所以在Stop()執行時有兩種情況:
* Timer還未觸發,系統協程已經刪除該Timer,Stop()返回false;
* Timer已經觸發,系統協程還未刪除該Timer,Stop()返回true;
綜上,停止一個Timer示意圖如下:

### 重置Timer
重置Timer時會先把timer從系統協程中刪除,修改新的時間后重新添加到系統協程中。
重置函數主要實現如下所示:
~~~go
func (t *Timer) Reset(d Duration) bool {
w := when(d)
active := stopTimer(&t.r)
t.r.when = w
startTimer(&t.r)
return active
}
~~~
其返回值與Stop()保持一致,即如果Timer成功停止,則返回true,如果Timer已經觸發,則返回false。
重置一個Timer示意圖如下:

由于新加的Timer時間很可能變化,所以其在系統協程的位置也會發生變化。
需要注意的是,按照官方說明,Reset()應該作用于已經停掉的Timer或者已經觸發的Timer,按照這個約定其返回值將總是返回false,之所以仍然保留是為了保持向前兼容,使用老版本Go編寫的應用不需要因為Go升級而修改代碼。
如果不按照此約定使用Reset(),有可能遇到Reset()和Timer觸發同時執行的情況,此時有可能會收到兩個事件,從而對應用程序造成一些負面影響,使用時一定要注意。
*****
【總結】
* NewTimer()創建一個新的Timer交給系統協程監控;
* Stop()通知系統協程刪除指定的Timer;
* Reset()通知系統協程刪除指定的Timer并再添加一個新的Timer;
- 概述
- go語言基礎特性
- Go語言聲明
- Go項目構建及編譯
- go command
- 程序設計原則
- Go基礎
- 變量
- 常量
- iota
- 基本類型
- byte和rune類型
- 類型定義和類型別名
- 數組
- string
- 高效字符串連接
- string底層原理
- 運算符
- new
- make
- 指針
- 下劃線 & import
- 語法糖
- 簡短變量申明
- 流程控制
- ifelse
- switch
- select
- select實現原理
- select常見案例
- for
- range
- range實現原理
- 常見案例
- range陷阱
- Goto&Break&Continue
- Go函數
- 函數
- 可變參數函數
- 高階函數
- init函數和main函數
- 匿名函數
- 閉包
- 常用內置函數
- defer
- defer常見案例
- defer規則
- defer與函數返回值
- defer實現原理
- defer陷阱
- 數據結構
- slice
- slice內存布局
- slice&array
- slice底層實現
- slice陷阱
- map
- Map實現原理
- 集合
- List
- Set
- 線程安全數據結構
- sync.Map
- Concurrent Map
- 面向對象編程
- struct
- 匿名結構體&匿名字段
- 嵌套結構體
- 結構體的“繼承”
- struct tag
- 行為方法
- 方法與函數
- type Method Value & Method Expressions
- interface
- 類型斷言
- 多態
- 錯誤機制
- error
- 自定義錯誤
- panic&recover
- reflect
- reflect包
- 應用示例
- DeepEqual
- 反射-fillObjectField
- 反射-copyObject
- IO
- 讀取文件
- 寫文件
- bufio
- ioutil
- Go網絡編程
- tcp
- tcp粘包
- udp
- HTTP
- http服務
- httprouter
- webSocket
- go并發編程
- Goroutine
- thread vs goroutine
- Goroutine任務取消
- 通過channel廣播實現
- Context
- Goroutine調度機制
- goroutine調度器1.0
- GMP模型調度器
- 調度器竊取策略
- 調度器的生命周期
- 調度過程全解析
- channel
- 無緩沖的通道
- 緩沖信道
- 單向信道
- chan實現原理
- 共享內存并發機制
- mutex互斥鎖
- mutex
- mutex原理
- mutex模式
- RWLock
- 使用信道處理競態條件
- WaitGroup
- 工作池
- 并發任務
- once運行一次
- 僅需任意任務完成
- 所有任務完成
- 對象池
- 定時器Timer
- Timer
- Timer實現原理
- 周期性定時器Ticker
- Ticker對外接口
- ticker使用場景
- ticker實現原理
- ticker使用陷阱
- 包和依賴管理
- package
- 依賴管理
- 測試
- 單元測試
- 表格測試法
- Banchmark
- BDD
- 常用架構模式
- Pipe-filter pattern
- Micro Kernel
- JSON
- json-內置解析器
- easyjson
- 性能分析
- gc
- 工具類
- fmt
- Time
- builtin
- unsafe
- sync.pool
- atomic
- flag
- runtime
- strconv
- template