定时器简介
skynet.timeout(ti,func)
skynet定时器非常的轻量级,仅仅提供注册函数,并且还不能传参。
定时器的实现仅仅用了300行代码。
定时器注册回调流程
通过调用capi把定时任务加入,最终以消息的方式传回本actor,用worker线程处理消息,执行注册函数。
function skynet.timeout(ti, func)
local session = c.intcommand("TIMEOUT",ti)
....
end
static int lintcommand(lua_State *L) {
result = skynet_command(context, cmd, parm);
...
}
cmd_timeout(struct skynet_context * context, const char * param) {
...
skynet_timeout(context->handle, ti, session);
...
}
int
skynet_timeout(uint32_t handle, int time, int session) {
if (time <= 0) {
struct skynet_message message;
message.source = 0;
message.session = session;
message.data = NULL;
message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
if (skynet_context_push(handle, &message)) {
return -1;
}
} else {
struct timer_event event;
event.handle = handle;
event.session = session;
timer_add(TI, &event, sizeof(event), time);
}
return session;
}
源码原理分析
skynet的设计思想参考Linux内核动态定时器的机制,参考Linux动态内核定时器介绍linux 内核定时器 timer_list详解 - 海王 - 博客园
近期链表:0-255个滴答数的定时任务会加入。特征:链表里都是触发时间相同的
散列链表:256 <= interval <= 0xffffffff 加入。 特征:链表里触发时间不一定相同
滴答数: skynet的定时器精度为0.01秒,按照它算法的定义就是0.01为1个滴答数。
到期时间:每一个加入的定时任务都会给它标记一个到期时间(加入时定时器当前滴答数+定时滴答数)。
skynet每次update都会取当前时间减去上次update时间获取流逝的滴答数。
然后把流逝的滴答数一滴答一滴答的执行。
执行过程中只关心到期间隔时间为 0 到 255 滴答数的处理。暂时不关注256 <= interval <= 0xffffffff。
滴答滴答流逝过程中,每走完一次0到255就会有一个对应的散列链表被移动(其实就是重新加入定时器,会重新给它们定位。
skynet.h
int skynet_timeout(uint32_t handle, int time, int session); //注册定时任务
void skynet_updatetime(void); //更新定时任务处理
void skynet_timer_init(void); //初始化
数据结构
#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK (TIME_NEAR-1)
#define TIME_LEVEL_MASK (TIME_LEVEL-1)
struct timer_event {
uint32_t handle;
int session;
};
struct timer_node {
struct timer_node *next;
uint32_t expire;
};
struct link_list {
struct timer_node head;
struct timer_node *tail;
};
struct timer {
struct link_list near[TIME_NEAR]; //近期链表长度256 记录0到255间隔触发的定时任务
struct link_list t[4][TIME_LEVEL]; //散列链表记录256 到 0xffffffff间隔触发的定时任务
struct spinlock lock; //自旋锁
uint32_t time; //定时器自增的滴答数
uint32_t starttime; //程序开始时间
uint64_t current; //上次记录的系统时间
uint64_t current_point; //上次记录的从开机到现在的时间,不受系统时间修改影响
};
定时器驱动函数
sknyet定时间隔0.00025秒调一次skynet_updatetime,
skynet_updatetime会获取当前运行时间,然后减去上次记录时间,拿到一个流逝的滴答数,然后调用timer_update
static void *
thread_timer(void *p) {
....
for (;;) {
skynet_updatetime();
usleep(2500);
}
.....
}
void
skynet_updatetime(void) {
uint64_t cp = gettime();
if(cp < TI->current_point) {
skynet_error(NULL, "time diff error: change from %lld to %lld", cp, TI->current_point);
TI->current_point = cp;
} else if (cp != TI->current_point) {
uint32_t diff = (uint32_t)(cp - TI->current_point);
TI->current_point = cp;
TI->current += diff;
int i;
for (i=0;i<diff;i++) {
timer_update(TI);
}
}
}
static void
timer_update(struct timer *T) {
SPIN_LOCK(T);
// try to dispatch timeout 0 (rare condition)
timer_execute(T);
// shift time first, and then dispatch timer message
timer_shift(T);
timer_execute(T);
SPIN_UNLOCK(T);
}
处理到期的定时任务
static inline void
timer_execute(struct timer *T) {
int idx = T->time & TIME_NEAR_MASK; 取得当前处于0到255的哪个位置
while (T->near[idx].head.next) {
struct timer_node *current = link_clear(&T->near[idx]); //取出链表数据
SPIN_UNLOCK(T);
// dispatch_list don't need lock T
dispatch_list(current); //处理到期任务
SPIN_LOCK(T);
}
}
调整散列列表
static void
timer_shift(struct timer *T) {
int mask = TIME_NEAR;
uint32_t ct = ++T->time; //自增滴答数
if (ct == 0) { //add_node ct为0时会加入到 散列列表3,0位置
move_list(T, 3, 0);
} else {
uint32_t time = ct >> TIME_NEAR_SHIFT;
int i=0;
while ((ct & (mask-1))==0) { 前 7位都为0时满足条件,也就是每过255滴答数触发一次。
int idx=time & TIME_LEVEL_MASK; 取得散列列表的位置id
if (idx!=0) {
move_list(T, i, 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; //定时器当前滴答数
if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {
前7位可以不一样,7位以后必须一样,也就是说间隔触发时间只有0-255之间(特殊情况,current_time为0的时候time不为0会加入到散列链表 3,0位置)
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);
}
}