這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這篇文章簡單的介紹下golang time 包下定時器的實現,說道定時器,在我們開發過程中很常用,由於使用的情境不同,所以對定時器實際的實現也就不同,go的定時器並沒有使用SIGALARM訊號實現,而是採取最小堆的方式實現(源碼包中使用數組實現的四叉樹),使用這種方式定時精度很高,但是有的時候可能我們不需要這麼高精度的實現,為了更高效的利用資源,有的時候也會實現一個精度比較低的演算法。
跟golang定時器相關的入口主要有以下幾種方法:
<-time.Tick(time.Second)<-time.After(time.Second)<-time.NewTicker(time.Second).C<-time.NewTimer(time.Second).Ctime.AfterFunc(time.Second, func() { /*do*/ })time.Sleep(time.Second)
這裡我們以其中NewTicker為入口,NewTicker的源碼如下:
func NewTicker(d Duration) *Ticker {if d <= 0 {panic(errors.New("non-positive interval for NewTicker"))}c := make(chan Time, 1)t := &Ticker{C: c,r: runtimeTimer{// when(d)返回一個runtimeNano() + int64(d)的未來時(到期時間)//runtimeNano運行時當前納秒時間when: when(d),period: int64(d), // 被喚醒的時間f: sendTime, // 時間到期後的回呼函數arg: c, // 時間到期後的斷言參數},}// 將新的定時任務添加到時間堆中// 編譯器會將這個函數翻譯為runtime.startTimer(t *runtime.timer)// time.runtimeTimer翻譯為runtime.timerstartTimer(&t.r)return t
這裡有個比較重要的是startTimer(&t.r)它的實現被翻譯在runtime包內
func startTimer(t *timer) {if raceenabled {racerelease(unsafe.Pointer(t))}addtimer(t)}func addtimer(t *timer) {lock(&timers.lock)addtimerLocked(t)unlock(&timers.lock)}
上面的代碼為了看著方便,我將他們都放在一起
下面代碼都寫出部分注釋
// 使用鎖將計時器添加到堆中// 如果是第一次運行此方法則啟動timerprocfunc addtimerLocked(t *timer) {if t.when < 0 {t.when = 1<<63 - 1}// t.i i是定時任務數組中的索引// 將新的定時任務追加到定時任務數組隊尾t.i = len(timers.t)timers.t = append(timers.t, t)// 使用數組實現的四叉樹最小堆根據when(到期時間)進行排序siftupTimer(t.i)// 如果t.i 索引為0if t.i == 0 {if timers.sleeping {// 如果還在sleep就喚醒timers.sleeping = false// 這裡基於OS的同步,並進行OS系統調用// 在timerproc()使goroutine從睡眠狀態恢複notewakeup(&timers.waitnote)}if timers.rescheduling {timers.rescheduling = false// 如果沒有定時器,timerproc()與goparkunlock共同sleep// goready這裡特殊說明下,線上程建立的堆棧,它比goroutine堆棧大。// 函數不能增長堆棧,同時不能被調度器搶佔goready(timers.gp, 0)}}if !timers.created {timers.created = truego timerproc() //這裡只有初始化一次}}// Timerproc已耗用時間驅動的事件。// 它sleep到計時器堆中的下一個。// 如果addtimer插入一個新的事件,它會提前喚醒timerproc。func timerproc() {timers.gp = getg()for {lock(&timers.lock)timers.sleeping = falsenow := nanotime()delta := int64(-1)for {if len(timers.t) == 0 {delta = -1break}t := timers.t[0]delta = t.when - nowif delta > 0 {break // 時間未到}if t.period > 0 {// 計算下一次時間 // period被喚醒的間隔t.when += t.period * (1 + -delta/t.period)siftdownTimer(0)} else {// remove from heaplast := len(timers.t) - 1if last > 0 {timers.t[0] = timers.t[last]timers.t[0].i = 0}timers.t[last] = niltimers.t = timers.t[:last]if last > 0 {siftdownTimer(0)}t.i = -1 // 標記移除}f := t.farg := t.argseq := t.sequnlock(&timers.lock)if raceenabled {raceacquire(unsafe.Pointer(t))}f(arg, seq)lock(&timers.lock)}if delta < 0 || faketime > 0 {// 沒有定時器,把goroutine sleep。timers.rescheduling = true// 將當前的goroutine放入等待狀態並解鎖鎖。// goroutine也可以通過呼叫goready(gp)來重新運行。goparkunlock(&timers.lock, "timer goroutine (idle)", traceEvGoBlock, 1)continue}// At least one timer pending. Sleep until then.timers.sleeping = truetimers.sleepUntil = now + delta// 重設noteclear(&timers.waitnote)unlock(&timers.lock)// 使goroutine進入睡眠狀態,直到notewakeup被調用,// 通過notewakeup 喚醒notetsleepg(&timers.waitnote, delta)}}
golang使用最小堆(最小堆是滿足除了根節點以外的每個節點都不小於其父節點的堆)實現的定時器。golang []*timer結構如下:
golang儲存定時任務結構
addtimer在堆中插入一個值,然後保持最小堆的特性,其實這個結構本質就是最小優先隊列的一個應用,然後將時間轉換一個絕對時間處理,通過睡眠和喚醒找出定時任務,這裡閱讀起來源碼很容易,所以只將代碼和部分注釋寫出。
我的部落格: 諾唯 | Noaway