skynet游戏服务器中的定时器

最近学习了skynet时间定时器的实现,写文整理下思路

定时器实现思路

游戏里经常需要定时去做一些任务,可以通过以下方式实现

  • 线程中调用各个Time处理函数 ,函数中比较是否到了触发时间,进行相应处理
  • 将定时任务组织成一个最少堆,每次只要判断堆顶到没到时间就行
  • 将定时任务组织成类似时间轮的结构,依次调度

以下本文将讨论skynet中timer实现(上述第3种)

定时器链表划分

  • 游戏服务器有个特点,随着策划内容的更新或bug修复,会有热更、重启的需求。很少有常年不停机维护的。

skynet中时间精度为0.01秒,1秒=100个tick
定时器按触发时间远近划分为5个组,near组含2 ^8个链表,t[0],t[1],t[2],t[3]组各含2 ^6个链表

时间事件插入规则

time值为1个32位无符号整数,按6+6+6+6+8划分后,依此决定放入哪个组
32----26--------20-----14-----8--------1
t[3] t[2] t[1] t[0] near
使用方法如下
tick值为 0~2 ^ 8-1时 将时间事件加入near组,该组含256个链表
tick再为 2 ^ 8 ~ 2 ^ (8+6)-1时 将时间事件放入t[0]组,该组含64个链表
tick再为 2 ^ (8+6) ~ 2 ^ (8+62)-1时 将时间事件放入t[1]组,该组含64个链表
tick再为 2 ^ (8+6
2) ~ 2 ^ (8+63)-1时 将时间事件放入t[2]组,该组含64个链表
tick再为 2 ^ (8+6
3) ~ 2 ^ (8+6*4)-1时 将时间事件放入t[3]组,该组含64个链表

  • t[3] t[2] t[1] t[0] near 这几个组,每个组里的时间粒度从右至左越来越大,使用时在细粒度的时间事件处理完成后,要将更粗粒度内的时间事件链表重新分配至细粒度中

定时器更新

skynet中会专门有个线程来循环调用skynet_updatetime,在时间变化>0.01秒后,会把对应链表下的事件进行触发操作。

并且会调用timer_shift来不断将后面组的时间事件插入合适位置。

static void
timer_shift(struct timer *T) {
	int mask = TIME_NEAR;
	uint32_t ct = ++T->time;
	if (ct == 0) { // 发生了回绕事件,服务器应该是开了很长时间。。。
		move_list(T, 3, 0);// 为什么这样处理,还未明白。
	} else {
		uint32_t time = ct >> TIME_NEAR_SHIFT;//
		int i=0;
        
        // 检测t[3]t[2]t[1]t[0]near,该从哪一段中取一个时间事件链表来移动了,每256次skynet_updatetime会触发一次这个调度
		while ((ct & (mask-1))==0) {// 当前比较near段全为0时
			int idx=time & TIME_LEVEL_MASK;// 找出当前t[0]时间对应的idx
			if (idx!=0) { // 若当前时间中t[0]段idx符合触发条件
				move_list(T, i, idx);// 将t[0]段idx所指链表重新分配至细粒度的链表中
				break;				
			}
			mask <<= TIME_LEVEL_SHIFT;
			time >>= TIME_LEVEL_SHIFT;
			++i;
		}
	}
}

// 将时间事件链表重新插入至合适位置
static void
move_list(struct timer *T, int level, int idx) {
	struct timer_node *current = link_clear(&T->t[level][idx]);
	while (current) {
		struct timer_node *temp=current->next;
		add_node(T,current);
		current=temp;
	}
}

static void
add_node(struct timer *T,struct timer_node *node) {
	uint32_t time=node->expire;
	uint32_t current_time=T->time;
	// 在current_time值与node->expire属于相同区段时,也就是看current_time当前走到了t[n]或near中的哪一段,就把这一段内的node分配至near链表中合适的位置
	if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) { 
		link(&T->near[time&TIME_NEAR_MASK],node);
	} else {// 还没到的,往后面放放
		int i;
		uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
		for (i=0;i<3;i++) {
			if ((time|(mask-1))==(current_time|(mask-1))) {
				break;
			}
			mask <<= TIME_LEVEL_SHIFT;
		}

		link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);	
	}
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nwao7890

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值