- Published on
skynet定时器
- Authors

- Name
- Ushen
skynet的定时器,timer线程会不断更新当前所有的定时器,时间到了将消息推送到队列
static void *
thread_timer(void *p) {
struct monitor * m = p;
skynet_initthread(THREAD_TIMER);
for (;;) {
skynet_updatetime();
skynet_socket_updatetime();
CHECK_ABORT
wakeup(m,m->count-1);
usleep(2500);
if (SIG) {
signal_hup();
SIG = 0;
}
}
// wakeup socket thread
skynet_socket_exit();
// wakeup all worker thread
pthread_mutex_lock(&m->mutex);
m->quit = 1;
pthread_cond_broadcast(&m->cond);
pthread_mutex_unlock(&m->mutex);
return NULL;
}
先看timer的结构体和定义,这里的starttime和current是用clock_gettime获取的
函数"clock_gettime"是基于Linux C语言的时间函数,他可以用于计算精度和纳秒
extern int clock_gettime (clockid_t __clock_id, structtimespec *__tp) __THROW;
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_s long_t tv_nsec; /* Nanoseconds. */
};
__clock_id : 时钟类型,posix标准定义了下面的四种基本类型,Linux系统有其他的扩充
CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变。
CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间。需要注意是不是进程开始到当前代码的时间。
CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间。需要注意是不是线程开始到当前代码的时间。
这里用的参数是CLOCK_REALTIME,得到秒和纳秒
starttime是秒,current会被除10000000,所以是1/100秒
current_point是用参数CLOCK_MONOTONIC获取,这里的单位也是1/100秒
两个链表near 和 t
near是放时间较近的一些定时器。
t则是放其他的一些定时器,这里的一维长度4,代表着等级,根据时间长短分为四个等级
#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];
struct link_list t[4][TIME_LEVEL];
struct spinlock lock;
uint32_t time;
uint32_t starttime;
uint64_t current;
uint64_t current_point;
};
static struct timer * TI = NULL;
timer线程每次首先会调用skynet_updatetime这个方法,通过gettime获取系统开启到当前的时间cp
然后通过current_ponint算出这次刷新到上次刷新的一个插值diff。
然后执行diff次timer_update方法
static uint64_t
gettime() {
uint64_t t;
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti);
t = (uint64_t)ti.tv_sec * 100;
t += ti.tv_nsec / 10000000;
return t;
}
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);
}
}
}
先调用timer_execute看看有没有可以处理的定时器消息,然后用timer_shift偏移,然后再次查看又没有可以处理的消息。
再timer_shift中,每次会++T->time,代表shift的次数。
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;
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) {
move_list(T, 3, 0);
} else {
uint32_t time = ct >> TIME_NEAR_SHIFT;
int i=0;
while ((ct & (mask-1))==0) {
int idx=time & TIME_LEVEL_MASK;
if (idx!=0) {
move_list(T, i, idx);
break;
}
mask <<= TIME_LEVEL_SHIFT;
time >>= TIME_LEVEL_SHIFT;
++i;
}
}
}