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

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

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編寫一個簡單高效定時模塊

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

Golang使用heap編寫一個簡單高效定時模組

package timer import ( "container/heap" // Golang提供的heap庫 "fmt" "os" "runtime/debug" "sync" "time" ) const ( MIN_TIMER_INTERVAL = 1

如何用Java編寫一個簡單的服務和客戶機

exce 解決 對賬 location exceptio acc 明顯 隊列 客戶 今天我要向大家介紹的是自己編寫的一個比較簡單的服務器和客戶機程序,註意一下哦,比較簡單。好了,閑話休提,砸門直入主題。 小編先從客戶機和服務器的模型開始講解。

ffmpeg+sdl教程----編寫一個簡單的播放5(同步視訊到音訊)

    個人認為,這這部分教程的新增程式碼量雖然不是最多的,難度卻是最大的,重複看了多次才明白,因為有兩個問題的困擾,搞得還不清楚: 1.音訊和視訊既然都有各自的時間戳,各自按各自的時間戳來播放不就行了,為什麼還需要同步呢? 2.如果要把視訊同步到音訊,怎麼同步?或者說以什麼

ROS學習--(十二)編寫一個簡單的釋出(publisher)

1.mkdir -p ~/catkin_ws/src/beginner_tutorials/src 先在beginner_tutorials下建立一個src。 -p的意思是如果路徑中有不存在的資料夾,則建立 2。在新建的資料夾裡面新建cpp檔案,talker

編寫一個簡單登錄驗證需要記錄日誌,Servlet的Cookie

cookie java servlet j2ee javaweb 登錄驗證並記錄日誌之前介紹了如何使用Server、mysql、tomcat等知識點編寫了一個簡單的登錄驗證。但是現在有了一個新的需求,我想要在登錄成功的時候往數據庫記錄一條日誌,登錄失敗的時候也要記錄一下。這個日誌要記錄用戶

C#編寫一個在asp.net core 3.1下的簡單的corn模式的計劃任務和一個簡單定時

asp.net core 下,新增了一個BackgroundService用來實現能在後臺跑一個長久執行的任務,因此,也可以用來替換掉原來使用的static的Timer元件, Timer元件主要有以下幾個麻煩的地方 1.如果是需要長時間跑的定時任務,需要定義為static,,在asp.net core下,無法

【原】shell編寫一個簡單的jmeter自動化壓測腳本

image tac vbo 用戶數 osx dot png das uvc 在公司做壓力測試也挺長時間了,每次測試前環境數據準備都需要話費較長時間,所以一直在考慮能不能將整個過程實現自動化進行,於是就抽空寫了一個自動化腳本,當然這個腳本目前功能十分簡陋,代碼也不完善,很有很

JointCode.Shuttle,一個簡單高效的跨 AppDomain 通信的服務框架

args 一般來說 code 動態 bsp ati 技術分享 服務組 更多 JointCode.Shuttle 是一個用於 AppDomain 間通信的服務架構。 1. 什麽情況下使用 JointCode.Shuttle 在 .net / mono 開發中,一般不太需要

手把手教你編寫一個簡單的PHP模塊形態的後門

cpp rest xtu job ring 事先 們的 original call 看到Freebuf 小編發表的用這個隱藏於PHP模塊中的rootkit,就能持久接管服務器文章,很感興趣,苦無作者沒留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一個非常流行

編寫一個簡單的TCP服務端和客戶端

不同的 大連 終端 服務器端 com 讀寫 所有 字數 資料 下面的實驗環境是linux系統。 效果如下: 1.啟動服務端程序,監聽在6666端口上 2.啟動客戶端,與服務端建立TCP連接 3.建立完TCP連接,在客戶端上向服務端發送消息 4.斷開

編寫一個簡單的單元測試用例

ide bsp span log 加減乘除 self teardown __main__ str 開發一個簡單的計算器,用於計算兩個數的加減乘除,示例: 1 class Calculator(): 2 ‘‘‘實現簡單的加減乘除‘‘‘ 3 def _

使用js編寫一個簡單的運動框架

parse 獲取當前值 進一步 filter 獲取 win logs alt htm 下班後,,沒事搗鼓搗鼓個人的小愛好。 首先,說明我的這個運動框架(css所有屬性)也是常見的框架一種,健壯性並不是太好,對於新手學習倒是挺好,,若是大神,老司機請拐彎。

如何用VBS編寫一個簡單的惡搞腳本

程序 一個 復制 文件夾 組合 不支持 文字 sendkeys 選項 windows系統的電腦, 首先右擊桌面,選擇新建-文本文檔,在桌面上新建一個文本文檔; 隨後打開計算機或者是我的電腦,點擊其中的組織(xp系統多為工具),選擇下面的文件夾和搜索

在vue使用vuex 一個簡單的實例

第一個 改變 size sta ssa num span vue font 1.安裝vuex:npm install vuex --save 2.在main.js文件中引入vuex (請忽略其它代碼) 3.建一個vuex文件夾,然後在建一個store.js(這兩個文件名字

編寫一個簡單的Web應用

AC info 覆蓋 圖片 ram cnblogs 類目 基於 div 上一篇我們搭建了一個簡單的Web應用:http://www.cnblogs.com/lay2017/p/8468515.html 本文將基於上一篇搭建的應用,編寫一些內容 編寫Servlet類 編寫J

用 Go 編寫一個簡單的 WebSocket 推送服務

年輕 sync 狀態 升級 ati .com 客戶端 我們 png 用 Go 編寫一個簡單的 WebSocket 推送服務 本文中代碼可以在 github.com/alfred-zhong/wserver 獲取。 背景 最近拿到需求要在網頁上展示報警信息。以往報警信息都

在EF使用SQL執行簡單高效的增刪查操作

查詢、刪除 style AD sync 組合 select IT OS 因此 隨著平臺數據的積累,對於數據訪問的速度要求愈來愈高。優化後臺訪問性能,將是之後的一個重點任務。 但是,後臺在項目開發初期采用的是Abp(Lite DDD)框架,集成EnityFramework。因

練習題,使用多線程編寫一個簡單的文本處理工具

tar 處理工具 utf txt nco opened odin 文本處理工具 while 一. 練習題要求:  編寫一個簡單的文本處理工具,具備三個任務,一個接收用戶輸入,一個將用戶輸入的內容格式化成大寫,一個將格式化後的結果存入文件二. 分析:  三個任務,那就是三個線

Golangheap包原始碼分析

heap的實現使用到了小根堆,下面先對堆做個簡單說明 1. 堆概念     堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於(或不小於)其左孩子和右孩子節點的值。   最大堆和最小堆是二叉堆的兩種形式。   最大堆:根結點的鍵值是所有堆結點鍵值中最大者。   最小