作者:shihuaping0918@163.com,转载请注明作者
skynet的timer是做游戏用得比较频繁的一个功能,分析一下它的源码还是有意义的。而且核心的C源码除了timer和网络以外,已经基本分析得差不多了。其它都是跟lua c api相关,或者是跟lua交互比较多的。timer的源码在skynet-timer.c和skynet-timer.h中。
在开始看代码之前,请大家默念三遍:
1秒=1000毫秒
1毫秒=1000微秒
1微秒=1000纳秒
然后再默念三遍:
skynet中的时间精度是10ms级别。
如果念完了我们开始准备看代码,在看源码前,还要说明一下,skynet的外部定时器是分为两部分存的,一部分存在叫near的数组里,另一部分存在一个二维数组里,分为四个级别。
最后,如果对位运算不熟悉的话就请回吧。
typedef void (*timer_execute_func)(void *ud,void *arg);
#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)
//先搞清楚下面的单位
//1秒=1000毫秒 milliseconds
//1毫秒=1000微秒 microseconds
//1微秒=1000纳秒 nanoseconds
//整个timer中毫秒的精度都是10ms,
//也就是说毫秒的一个三个位,但是最小的位被丢弃
struct timer_event {
uint32_t handle; //即是设置定时器的来源,又是超时消息发送的目标
int session; //session,一个增ID,溢出了从1开始,所以不要设时间很长的timer
};
//链表
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; //程序启动的时间点,timestamp,秒数
uint64_t current; //从程序启动到现在的耗时,精度10毫秒级
uint64_t current_point; //当前时间,精度10毫秒级
};
static struct timer * TI = NULL;
//清空链表,返回链表第一个结点
static inline struct timer_node *
link_clear(struct link_list *list) {
struct timer_node * ret = list->head.next;
list->head.next = 0;
list->tail = &(list->head);
return ret;
}
//将结点放入链表
static inline void
link(struct link_list *list,struct timer_node *node) {
list->tail->next = node;
list->tail = node;
node->next=0;
}
//添加一个定时器结点
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)) {
link(&T->near[time&TIME_NEAR_MASK],node);
} else { //这里有一种特殊情况,就是当time溢出,回绕的时候
int i;
uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
for (i=0;i<3;i++) { //看到i<3没,很重要很重要
if ((time|(mask-1))==(current_time|(mask-1))) {
break;
}
mask <<= TIME_LEVEL_SHIFT; //mask越来越大
}
//放入数组中
link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
}
}
//添加一个定时器
static void
timer_add(struct timer *T,void *arg,size_t sz,int time) {
struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
memcpy(node+1,arg,sz);
SPIN_LOCK(T);
node->expire=time+T->time; //超时时间+当前计数
add_node(T,node);
SPIN_UNLOCK(T);
}
//移动某个级别的链表内容
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
timer_shift(struct timer *T) {
int mask = TIME_NEAR;
uint32_t ct = ++T->time;
if (ct == 0) { //time溢出了
move_list(T, 3, 0); //这里就是那个很重要的3
} else { //time正常
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; //mask越来越大
time >>= TIME_LEVEL_SHIFT; //time越来越小
++i;
}
}
}
//派发消息到目标服务消息队列
static inline void
dispatch_list(struct timer_node *current) {
do {
struct timer_event * event = (struct timer_event *)(current+1);
struct skynet_message message;
message.source = 0;
message.session = event->session; //这个很重要,接收侧靠它来识别是哪个timer
message.data = NULL;
message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
//派发定显示器消息
skynet_context_push(event->handle, &message);
struct timer_node * temp = current;
current=current->next;
skynet_free(temp);
} while (current); //一直到清空为止
}
//派发消息
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_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 struct timer *
timer_create_timer() {
struct timer *r=(struct timer *)skynet_malloc(sizeof(struct timer));
memset(r,0,sizeof(*r));
int i,j;
for (i=0;i<TIME_NEAR;i++) {
link_clear(&r->near[i]);
}
for (i=0;i<4;i++) {
for (j=0;j<TIME_LEVEL;j++) {
link_clear(&r->t[i][j]);
}
}
SPIN_INIT(r)
r->current = 0;
return r;
}
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;
}
// centisecond: 1/100 second
static void
systime(uint32_t *sec, uint32_t *cs) {
#if !defined(__APPLE__)
struct timespec ti;
clock_gettime(CLOCK_REALTIME, &ti);
*sec = (uint32_t)ti.tv_sec;
*cs = (uint32_t)(ti.tv_nsec / 10000000);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
*sec = tv.tv_sec; //1970/1/1到现在的秒数
*cs = tv.tv_usec / 10000; //微秒转毫秒,精度10ms
#endif
}
static uint64_t
gettime() {
uint64_t t;
#if !defined(__APPLE__)
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti);
t = (uint64_t)ti.tv_sec * 100;
t += ti.tv_nsec / 10000000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
t = (uint64_t)tv.tv_sec * 100; //秒数
t += tv.tv_usec / 10000; //精度为10毫秒级
#endif
return t;
}
//在线程中不断被调用
//调用时间 间隔为 2500微秒,即2.5毫秒
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); //注意这里
}
}
}
//返回启动的时的timestamp
uint32_t
skynet_starttime(void) {
return TI->starttime;
}
//返回耗时
uint64_t
skynet_now(void) {
return TI->current;
}
//初始化
void
skynet_timer_init(void) {
TI = timer_create_timer();
uint32_t current = 0;
systime(&TI->starttime, ¤t); //取starttime和current
TI->current = current;
TI->current_point = gettime();
}
// for profile
#define NANOSEC 1000000000
#define MICROSEC 1000000
uint64_t
skynet_thread_time(void) {
#if !defined(__APPLE__)
struct timespec ti;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ti);
return (uint64_t)ti.tv_sec * MICROSEC + (uint64_t)ti.tv_nsec / (NANOSEC / MICROSEC);
#else
struct task_thread_times_info aTaskInfo;
mach_msg_type_number_t aTaskInfoCount = TASK_THREAD_TIMES_INFO_COUNT;
if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, (task_info_t )&aTaskInfo, &aTaskInfoCount)) {
return 0;
}
return (uint64_t)(aTaskInfo.user_time.seconds) + (uint64_t)aTaskInfo.user_time.microseconds;
#endif
}
代码分析完了,skynet中有一个timer线程,每2.5毫秒就更新一下timer中的时间。每次更新都会对一个叫time的计数器做加1操作,所以这个计数器其实可以当作时间来看待,然后对near数组中的定时器进行触发。
前面讲到还有四个级别的定时器数组,这些数组在timer_shift中被不断地重新调整优先级,直到移动到near数组中。四个级别分别是0,1,2,3,级别越大,expire也就越大,也就是超时时间越大。timer_shift会对各个级别中的定时器做重新的级别分配。
time计数器是有可能溢出的,有人称它为回绕,当它溢出了以后,会做一个特殊处理。即对level3的整个数组进行重新级别分配。
2017.8.24
还是补充说明一下,位运算在timer四个级别中的作用吧。一个time32位,初始near使用低7位作掩码,然后是13位掩码,然后19位,然后25位。
time和current_time都是对mask做与操作,与操作就是0 | 0 = 0 其它的都为1,这个操作意味着什么呢,就意味着a | b,结果肯定是两者中最大的,或者比两个都要大。因为a,b中只要有一个对应位置分别是0,1,最终都会变成1。而mask-1所有的位都是1,所以取到的值在mask位的肯定全为1。near取的是后8位,后8位的值也就是256,也就是说如果time和current_time只差255,那么时间就真的很接近了,255*2.5毫秒不到一秒钟。mask位数越多,被mask放大的数字也就越大。当然最高位也必须是相等的才行。
为什么time溢出了要把t[3]的定时器重新分配呢?这是因为time和current_time都特别大的时候,低8位(near),8+6,8+62,这三个值的位数全设成1,不能够让time|mask等于current_time|mask。反而是8+63能够让time|mask==current_time|mask。所以全放到t[3]里面。而当time溢出了以后,current_time也会变。所以它要把t[3]全放进near里。换句话说,就是t[3]在timer溢出之前实际上充当了near的角色!
希望补充的这一段能够让多数人明白,这个算法的原理。