Golang中使用heap编写一个简单高效的定时器模块

定时器模块在服务端开发中非常重要,一个高性能的定时器模块能够大幅度提升引擎的运行效率。使用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()
}

  

时间: 2024-10-17 08:49:06

Golang中使用heap编写一个简单高效的定时器模块的相关文章

用 C 语言编写一个简单的垃圾回收器

人们似乎认为编写垃圾回收机制是很难的,是一种只有少数智者和Hans Boehm(et al)才能理解的高深魔法.我认为编写垃圾回收最难的地方就是内存分配,这和阅读K&R所写的malloc样例难度是相当的. 在开始之前有一些重要的事情需要说明一下:第一,我们所写的代码是基于Linux Kernel的,注意是Linux Kernel而不是GNU/Linux.第二,我们的代码是32bit的.第三,请不要直接使用这些代码.我并不保证这些代码完全正确,可能其中有一些我 还未发现的小的bug,但是整体思路仍

【C++】编写一个简单的函数实现重载。

//编写一个简单的函数实现重载. #include <iostream> using namespace std; int max(int a,int b) { return a>b?a:b; } int max(int a,int b,int c) { int x=max(a,b); return max(x,c); } double max(double a,double b) { return a>b?a:b; } int main() { cout<<"

手把手教你编写一个简单的PHP模块形态的后门

看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web server端的script语言.目前很多web应用程序都基于php语言实现.由于php是个开源软件并易于扩展,所以我们可以通过编写一个PHP模块(module 或者叫扩展 extension)来实现一个Backdoor. 本文就简单介下如何一步步编写一个简单的php 动态扩展后门. 0×01. p

编写一个简单的javaEE加法程序

一 .javaEE的安装及环境配置 工具: 32位系统准备eclipse-jee-mars-2-win32.zip,64位系统准备eclipse-jee-mars-2-win32-x86_64.zip jdk1.7 maven3.3.9.rar m2.rar 环境配置: 1. 设置eclipse的配置文件eclipse.ini,修改虚拟机路径,在-vmargs之前添加 -vm E:\jee\jdk1.7\bin\javaw.exe 注意:用写字板打开修改,-vm有的电脑要换行,有的电脑不用换行

Swift语言编写一个简单的条形码扫描APP

swift语言编写一个简单的条形码扫描APP 原文地址:appcoda 在处理职员在杂货店的收银台排了很长的队伍,在机场帮助检查背包和旅客,或者在主要的食品供应商,协助处理乏味的存货清单过程,条形码扫描是很简单的处理工具.实际上,他们已经用了这个办法来解决消费者在智能购物,图书分类,等其他目的.因此,让我们来制作一个iPhone版本的条形码扫描工具吧! 对我们来说幸运的是,苹果已经制作了条形码扫描的程序,实现它是一件很简单的事情.我们将要研究进入AV Foundation框架的世界,组建APP,

编写一个简单的Jquery插件

1.实现内容 定义一个简单的jquery插件,alert传递进来的参数 2.插件js文件(jquery.showplugin.js) (function ($) { //定义插件中的方法 var methods = { //Object showName: function (name) { alert('Name:' + name); }, showAge: function (age) { alert('Age' + age); } }; //method方法名 $.fn.showplugi

练习题,使用多线程编写一个简单的文本处理工具

一. 练习题要求: 编写一个简单的文本处理工具,具备三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件 二. 分析: 三个任务,那就是三个线程.分别的输入,转换,写入,这3个线程.那么写入的输入,其他线程怎么取出?我们可以使用Queue队列,把用户输入的字符写入到队列中,然后再进行转换.转换完成后,再写入到另外一个队列中.等待最后写入文件三.实现 1 from threading import Thread 2 from multiprocessing i

【C++】编写一个简单的类。包含构造函数,成员函数等。

<pre name="code" class="cpp">//编写一个简单的类.包含构造函数,成员函数等. #include <iostream> using namespace std; class Rec { public: Rec(int l,int w); int Area(); void Print(); private: int length,wide; }; Rec::Rec(int l,int w) { length=l; w

编写一个简单的内核驱动模块时报错 “/lib/modules/3.13.0-32-generic/bulid: 没有那个文件或目录。 停止。”

编写一个简单的内核驱动模块 1 static int hello_init() 2 { 3 printk("hello,I am in kernel now\n"); 4 return 0; 5 } 6 void addfunc(int a,int b) 7 {return a+b;} 8 static void hello_exit() 9 { 10 printk("hello ,I will leave the kernel now\n"); 11 } 12 m