1. 程式人生 > >Golang中使用heap編寫一個簡單高效的定時器模塊

Golang中使用heap編寫一個簡單高效的定時器模塊

true pop 邏輯 .com light 初始化 callback before cell

定時器模塊在服務端開發中非常重要,一個高性能的定時器模塊能夠大幅度提升引擎的運行效率。使用Golang和heap實現一個通用的定時器模塊,代碼來自:https://github.com/xiaonanln/goTimer

也可以查看文檔:http://godoc.org/github.com/xiaonanln/goTimer,下面是完整的代碼,並進行適當的註釋和分析。

從性能測試結果來看,基於heap的定時器模塊在效率上並不會比時間輪(TimeWheel)的實現慢多少,但是邏輯上要簡單很多。

源代碼加註釋:

package timer

import (
	"container/heap" // Golang提供的heap庫
	"fmt"
	"os"
	"runtime/debug"
	"sync"
	"time"
)

const (
	MIN_TIMER_INTERVAL = 1 * time.Millisecond // 循環定時器的最小時間間隔
)

var (
	nextAddSeq uint = 1 // 用於為每個定時器對象生成一個唯一的遞增的序號
)

// 定時器對象
type Timer struct { 
	fireTime  time.Time // 觸發時間
	interval  time.Duration // 時間間隔(用於循環定時器)
	callback  CallbackFunc // 回調函數
	repeat    bool // 是否循環
	cancelled bool // 是否已經取消
	addseq    uint // 序號
}

// 取消一個定時器,這個定時器將不會被觸發
func (t *Timer) Cancel() {
	t.cancelled = true
}

// 判斷定時器是否已經取消
func (t *Timer) IsActive() bool {
	return !t.cancelled
}

// 使用一個heap管理所有的定時器
type _TimerHeap struct {
	timers []*Timer
}

// Golang要求heap必須實現下面這些函數,這些函數的含義都是不言自明的

func (h *_TimerHeap) Len() int {
	return len(h.timers)
}

// 使用觸發時間和需要對定時器進行比較
func (h *_TimerHeap) Less(i, j int) bool {
	//log.Println(h.timers[i].fireTime, h.timers[j].fireTime)
	t1, t2 := h.timers[i].fireTime, h.timers[j].fireTime
	if t1.Before(t2) {
		return true
	}

	if t1.After(t2) {
		return false
	}
	// t1 == t2, making sure Timer with same deadline is fired according to their add order
	return h.timers[i].addseq < h.timers[j].addseq
}

func (h *_TimerHeap) Swap(i, j int) {
	var tmp *Timer
	tmp = h.timers[i]
	h.timers[i] = h.timers[j]
	h.timers[j] = tmp
}

func (h *_TimerHeap) Push(x interface{}) {
	h.timers = append(h.timers, x.(*Timer))
}

func (h *_TimerHeap) Pop() (ret interface{}) {
	l := len(h.timers)
	h.timers, ret = h.timers[:l-1], h.timers[l-1]
	return
}

// 定時器回調函數的類型定義
type CallbackFunc func()

var (
	timerHeap     _TimerHeap // 定時器heap對象
	timerHeapLock sync.Mutex // 一個全局的鎖
)

func init() {
	heap.Init(&timerHeap) // 初始化定時器heap
}

// 設置一個一次性的回調,這個回調將在d時間後觸發,並調用callback函數
func AddCallback(d time.Duration, callback CallbackFunc) *Timer {
	t := &Timer{
		fireTime: time.Now().Add(d),
		interval: d,
		callback: callback,
		repeat:   false,
	}
	timerHeapLock.Lock() // 使用鎖規避競爭條件
	t.addseq = nextAddSeq 
	nextAddSeq += 1

	heap.Push(&timerHeap, t)
	timerHeapLock.Unlock()
	return t
}

// 設置一個定時觸發的回調,這個回調將在d時間後第一次觸發,以後每隔d時間重復觸發,並調用callback函數
func AddTimer(d time.Duration, callback CallbackFunc) *Timer {
	if d < MIN_TIMER_INTERVAL {
		d = MIN_TIMER_INTERVAL
	}

	t := &Timer{
		fireTime: time.Now().Add(d),
		interval: d,
		callback: callback,
		repeat:   true, // 設置為循環定時器
	}
	timerHeapLock.Lock()
	t.addseq = nextAddSeq // set addseq when locked
	nextAddSeq += 1

	heap.Push(&timerHeap, t)
	timerHeapLock.Unlock()
	return t
}

// 對定時器模塊進行一次Tick
// 
// 一般上層模塊需要在一個主線程的goroutine裏按一定的時間間隔不停的調用Tick函數,從而確保timer能夠按時觸發,並且
// 所有Timer的回調函數也在這個goroutine裏運行。
func Tick() {
	now := time.Now()
	timerHeapLock.Lock()

	for {
		if timerHeap.Len() <= 0 { // 沒有任何定時器,立刻返回
			break
		}

		nextFireTime := timerHeap.timers[0].fireTime
		if nextFireTime.After(now) { // 沒有到時間的定時器,返回
			break
		}

		t := heap.Pop(&timerHeap).(*Timer)

		if t.cancelled { // 忽略已經取消的定時器
			continue
		}

		if !t.repeat {
			t.cancelled = true
		}
<strong>		// 必須先解鎖,然後再調用定時器的回調函數,否則可能導致死鎖!!!</strong>
		timerHeapLock.Unlock() 
		runCallback(t.callback) // 運行回調函數並捕獲panic
		timerHeapLock.Lock()

		if t.repeat { // 如果是循環timer就把Timer重新放回heap中
			// add Timer back to heap
			t.fireTime = t.fireTime.Add(t.interval)
			if !t.fireTime.After(now) {
				t.fireTime = now.Add(t.interval)
			}
			t.addseq = nextAddSeq
			nextAddSeq += 1
			heap.Push(&timerHeap, t)
		}
	}
	timerHeapLock.Unlock()
}

// 創建一個goroutine對定時器模塊進行定時的Tick
func StartTicks(tickInterval time.Duration) {
	go selfTickRoutine(tickInterval)
}

func selfTickRoutine(tickInterval time.Duration) {
	for {
		time.Sleep(tickInterval)
		Tick()
	}
}

// 運行定時器的回調函數,並捕獲panic,將panic轉化為錯誤輸出
func runCallback(callback CallbackFunc) {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Callback %v paniced: %v\n", callback, err)
			debug.PrintStack()
		}
	}()
	callback()
}

  

Golang中使用heap編寫一個簡單高效的定時器模塊