最近学习了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+62) ~ 2 ^ (8+63)-1时 将时间事件放入t[2]组,该组含64个链表
tick再为 2 ^ (8+63) ~ 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);
}
}