golang 高效低精度定时器实现

golang默认定时器是通过time模块提供的,不管是golang,libev,libevent也好,定时器都是通过最小堆实现的,导致加入定时器时间复杂度为O(lgn),在需要大量定时器时效率较低,所以Linux提供了基于时间轮的实现,我们本次提供的

定时器实现就是标准的Linux时间轮实现方式。当然,我是把Skynet(https://github.com/cloudwu/skynet/blob/master/skynet-src/skynet_timer.c)的定时器移植了过来,偷窃无罪。。。

贴一张Linux时间轮的数据结构,如果比较陌生的话可以参考一下两篇文章:

1.http://www.ibm.com/developerworks/cn/linux/l-cn-timers/

2.http://blog.csdn.net/lonewolfxw/article/details/8034395

先看一下如何使用

package timer

import (
	"fmt"
	"sync/atomic"
	"testing"
	"time"
)

var sum int32 = 0
var N int32 = 300
var tt *Timer

func now() {
	fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
	atomic.AddInt32(&sum, 1)
	v := atomic.LoadInt32(&sum)
	if v == 2*N {
		tt.Stop()
	}

}

func TestTimer(t *testing.T) {
	timer := New(time.Millisecond * 10)
	tt = timer
	fmt.Println(timer)
	var i int32
	for i = 0; i < N; i++ {
		timer.NewTimer(time.Millisecond*time.Duration(10*i), now)
		timer.NewTimer(time.Millisecond*time.Duration(10*i), now)
	}
	timer.Start()
	if sum != 2*N {
		t.Error("failed")
	}
}

timer := New(time.Millisecond * 10)我们定义了一个tick为0.01s的定时器,循环了300次每次启动2个timeout,回调中将sum+1,所以最后sum应该等于600。

这是我golang的处女作,可能代码不是很规范,有空请多review review

github地址:https://github.com/Skycrab/code/blob/master/Go/timer/timer.go

package timer

import (
	"container/list"
	"fmt"
	"sync"
	"time"
)

//referer https://github.com/cloudwu/skynet/blob/master/skynet-src/skynet_timer.c

const (
	TIME_NEAR_SHIFT  = 8
	TIME_NEAR        = 1 << TIME_NEAR_SHIFT
	TIME_LEVEL_SHIFT = 6
	TIME_LEVEL       = 1 << TIME_LEVEL_SHIFT
	TIME_NEAR_MASK   = TIME_NEAR - 1
	TIME_LEVEL_MASK  = TIME_LEVEL - 1
)

type Timer struct {
	near [TIME_NEAR]*list.List
	t    [4][TIME_LEVEL]*list.List
	sync.Mutex
	time uint32
	tick time.Duration
	quit chan struct{}
}

type Node struct {
	expire uint32
	f      func()
}

func (n *Node) String() string {
	return fmt.Sprintf("Node:expire,%d", n.expire)
}

func New(d time.Duration) *Timer {
	t := new(Timer)
	t.time = 0
	t.tick = d
	t.quit = make(chan struct{})

	var i, j int
	for i = 0; i < TIME_NEAR; i++ {
		t.near[i] = list.New()
	}

	for i = 0; i < 4; i++ {
		for j = 0; j < TIME_LEVEL; j++ {
			t.t[i][j] = list.New()
		}
	}

	return t
}

func (t *Timer) addNode(n *Node) {
	expire := n.expire
	current := t.time
	if (expire | TIME_NEAR_MASK) == (current | TIME_NEAR_MASK) {
		t.near[expire&TIME_NEAR_MASK].PushBack(n)
	} else {
		var i uint32
		var mask uint32 = TIME_NEAR << TIME_LEVEL_SHIFT
		for i = 0; i < 3; i++ {
			if (expire | (mask - 1)) == (current | (mask - 1)) {
				break
			}
			mask <<= TIME_LEVEL_SHIFT
		}

		t.t[i][(expire>>(TIME_NEAR_SHIFT+i*TIME_LEVEL_SHIFT))&TIME_LEVEL_MASK].PushBack(n)
	}

}

func (t *Timer) NewTimer(d time.Duration, f func()) *Node {
	n := new(Node)
	n.f = f
	t.Lock()
	n.expire = uint32(d/t.tick) + t.time
	t.addNode(n)
	t.Unlock()
	return n
}

func (t *Timer) String() string {
	return fmt.Sprintf("Timer:time:%d, tick:%s", t.time, t.tick)
}

func dispatchList(front *list.Element) {
	for e := front; e != nil; e = e.Next() {
		node := e.Value.(*Node)
		go node.f()
	}
}

func (t *Timer) moveList(level, idx int) {
	vec := t.t[level][idx]
	front := vec.Front()
	vec.Init()
	for e := front; e != nil; e = e.Next() {
		node := e.Value.(*Node)
		t.addNode(node)
	}
}

func (t *Timer) shift() {
	t.Lock()
	var mask uint32 = TIME_NEAR
	t.time++
	ct := t.time
	if ct == 0 {
		t.moveList(3, 0)
	} else {
		time := ct >> TIME_NEAR_SHIFT
		var i int = 0
		for (ct & (mask - 1)) == 0 {
			idx := int(time & TIME_LEVEL_MASK)
			if idx != 0 {
				t.moveList(i, idx)
				break
			}
			mask <<= TIME_LEVEL_SHIFT
			time >>= TIME_LEVEL_SHIFT
			i++
		}
	}
	t.Unlock()
}

func (t *Timer) execute() {
	t.Lock()
	idx := t.time & TIME_NEAR_MASK
	vec := t.near[idx]
	if vec.Len() > 0 {
		front := vec.Front()
		vec.Init()
		t.Unlock()
		// dispatch_list don‘t need lock
		dispatchList(front)
		return
	}

	t.Unlock()
}

func (t *Timer) update() {
	// try to dispatch timeout 0 (rare condition)
	t.execute()

	// shift time first, and then dispatch timer message
	t.shift()

	t.execute()

}

func (t *Timer) Start() {
	tick := time.NewTicker(t.tick)
	defer tick.Stop()
	for {
		select {
		case <-tick.C:
			t.update()
		case <-t.quit:
			return
		}
	}
}

func (t *Timer) Stop() {
	close(t.quit)
}

熟悉skynet的童鞋看这段代码应该很熟悉,期待出现了golang大牛也写个牛逼的游戏服务器框架,这应该是很多人的心声吧。

时间: 2024-10-29 19:10:41

golang 高效低精度定时器实现的相关文章

网络游戏中的(低精度)时间同步

对于网络游戏来说,从物体的移动.攻击到最基础的计时等等,都需要客户端与服务器保持时间的相对一致,那么服务器与客户端同步便是一个必须要解决的问题.通常,网络游戏都会利用心跳来进行同步,那么当客户端并不需要如此精度的同步时,有没有其他方法呢?这里主要讨论低精度的时间同步(精确到秒). 工作中接触过3种简单的时间同步方法: 首先,定义时间同步类 /// 32位操作系统 typedef unsigned int64_t QWORD; typedef unsigned long DWORD; class

ORACLE 中NUMBER 类型 低精度转换成高精度

例如: 表User中有一个字段 salary  Number(10,3), 如果想把字段salary的类型提高精度到salary  Number(10,6),保留六位小数, 解决办法:1,ALTER TABEL USER MODIFY SALARY NUMBER(13,6); 解释:number类型刚开始是,长度10位,3位小数,如果想增加3位小数,对应的长度也必须增加,否则无法修改.所以NUMBER(13,6);这样就可以提高精度了, ORACLE 中NUMBER 类型 低精度转换成高精度

int类型被强制转换成较低精度的byte类型

公司的项目上线之前会进行代码合规性检查,其中很容易违反的一个规则就是“不要把原始类型转换成较低的精度”,实际开发的过程中,很多方法在处理数据时,尤其在做移位操作的时候,难免要把int类型转换成byte类型,这时候就不可避免得出现原始类型转换成较低精度的情况,没有什么简便的方法,只能通过调用ByteArrayOutputStream,DataOutputStream 来处理.把处理过程封装到一个函数中.然后在进行类型转化的时候调用函数.处理函数如下: public byte intToButeAr

Golang 高效实践之并发实践context篇

前言 在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel.比如说我们可以用channel来控制几个goroutine的同步和退出时机,但是我们需要close channel通知其他接受者,当通知和通信的内容混在一起时往往比较复杂,需要把握好channel的读写时机,以及不能往已经关闭的channel中再写入数据.如果有没有一种更好的上下文控制机制呢?答案就是文章今天要介绍的context,

golang 浮点数 取精度的效率对比

需求 浮点数取2位精度输出 实现 代码 package main import ( "time" "log" "strconv" "fmt" ) func main() { threadCount := 100000 fa := 3.233667 time1 := TestFn(threadCount,fa,func(fa float64) string{ return strconv.FormatFloat(fa,'f',2

Golang 高效实践之并发实践

前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面我会跟大家详细的介绍一些在实际生产编程中很容易踩坑的知识点. CSP 在介绍Golang的并发实践前,有必要先介绍简单介绍一下CSP理论.CSP,全称是Communicating sequential processes,翻译为通信顺序进程,又翻译为交换消息的顺序程序,用来描述并发性系统的交互模式.

Golang 完成一个 Crontab定时器(1)

前言 Linux的Crontab定时器似乎已经足够强大,但是我认为还是没有办法满足我们所有的需求,例如定时器某一瞬间需要动态添加/删除任务的功能,例如定时器只能在指定的节点上启动(主节点),其他节点不需要定时服务,这种情况Linux自带的Crontab就不能够满足我们的需求了,所以这次要徒手定义一个Crontab定时器,作为自己的备用. 需求分析 看我博客的基本也都知道,做任何事,都要进行一个需求分析.既然是一个定时器,那么应该支持的功能如下 定时启动任务(废话) 支持基础的Crontab语法

[tem]高精度与低精度

1 //倒着存 B取的低精最大值所以简化了一点 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cmath> 6 #include <algorithm> 7 using namespace std; 8 const int N=1005,B=1e4,W=4,L=1005; 9 struct people{ 10 int a,b,t; 11 }p

【POJ2325】Persistent Numbers 贪心+高精度/低精度

题意:我们可以把一个数A变成B=A的各位乘积,现在给出B,求是否可以有某个A通过计算得到B,有的话,是多少. 题解:贪心. 我们先分解B,若质因数有大于等于10的显然就不行了. 否则则一定可以把他的各因数排在一起成为A,使A的各位乘积=B. 贪心策略:把小数放前面. 注意: 一.不一定要质因数,10以内即可. 二.需要高精度. 三.A!=B 代码: #include <cstdio> #include <cstring> #include <algorithm> #de