Skynet定时器实现方案
skynet中定时器数据结构
采用时间轮方式,hash表+链表实现,
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[256]; // 即将到来的定时器
struct link_list t[4][64]; // 相对较遥远的定时器
struct spinlock lock;
uint32_t time; // 记录当前滴答数
uint64_t starttime;
uint64_t current;
uint64_t current_point;
};
其中time
为32
位无符号整数, 记录时间片对应数组near[256]
,表示即将到来的定时任务, t[4][64]
,表示较为遥远的定时任务。
定时器执行流程
skynet_time_wheel
t[3] | t[2] | t[1] | t[0] | near |
---|---|---|---|---|
26-32位 | 20-26位 | 14-20位 | 8-14位 | 0-8位 |
[2^(8+6x3),2^(8+6x4)-1] |
[2^(8+6x2),2^(8+6x3)-1] |
[2^(8+6),2^(8+6x2)-1] |
[2^8,2^(8+6) -1] |
[0,2^8-1] |
- 首先检查节点的
expire
与time
的高24位是否相等,相等则将该节点添加到expire
低8位值对应数组near
的元素的链表中,不相等则进行下一步 - 检查
expire
与time
的高18位是否相等,相等则将该节点添加到expire
低第9位到第14位对应的6位二进制值对应数组t[0]
的元素的链表中,否则进行下一步 - 检查
expire
与time
的高12位是否相等,相等则将该节点添加到expire
低第15位到第20位对应的6位二进制值对应数组t[1]
的元素的链表中,如果不相等则进行下一步 - 检查
expire
与time
的高6位是否相等,相等则将该节点添加到expire低第21位到第26位对应的6位二进制值对应数组t[2]
的元素的链表中,如果不相等则进行下一步 - 将该节点添加到
expire
低第27位到第32位对应的6位二进制值对应数组t[3]
的元素的链表中
以下为具体实现,仅贴出主要接口,具体细节请参考skynet
源代码。
定时器初始化
// skynet_start.c
// skynet 启动入口
void
skynet_start(struct skynet_config * config) {
...
skynet_timer_init();
...
}
// skynet_timer.c
void
skynet_timer_init(void) {
// 创建全局timer结构 TI
TI = timer_create_timer();
uint32_t current = 0;
systime(&TI->starttime, ¤t);
TI->current = current;
TI->current_point = gettime();
}
添加定时器
通过skynet_server.c
中的cmd_timeout
调用skynet_timeout
添加新的定时器
// TI为全局的定时器指针
static struct timer * TI = NULL;
int skynet_timeout(uint32_t handle, int time, int session) {
...
struct timer_event event;
event.handle = handle; // callback
eveng.session = session;
// 添加新的定时器节点
timer_add(TI, &event, sizeof(event), time);
return session;
}
// 添加新的定时器节点
static void timer_add(struct timer *T, void 8arg, size_t sz, int time) {
// 给timer_node指针分配空间,还需要分配timer_node + timer_event大小的空间,
// 之后通过node + 1可获得timer_event数据
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);
}
// 添加到定时器链表里,如果定时器的到期滴答数跟当前比较近(<2^8),表示即将触发定时器添加到T->near数组里
// 否则根据差值大小添加到对应的T->T[i]中
static void add_node(struct timer *T, struct timer_node *node) {
...
}
驱动方式
skynet
启动时,会创建一个线程专门跑定时器,每帧(0.0025s
)调用skynet_updatetime()
// skynet_start.c
static void *
thread_timer(void *p) {
struct monitor * m = p;
skynet_initthread(THREAD_TIMER);
for (;;) {
skynet_updatetime(); // 调用timer_update
skynet_socket_updatetime();
CHECK_ABORT
wakeup(m,m->count-1);
usleep(2500); // 2500微秒 = 0.0025s
if (SIG) {
signal_hup();
SIG = 0;
}
}
...
}
每个定时器设置一个到期滴答数,与当前系统的滴答数(启动时为0,1滴答1滴答往后跳,1滴答==0.01s ) 比较得到差值interval;
如果interval比较小(0 <= interval <= 2^8-1),表示定时器即将到来,保存在第一部分前2^8个定时器链表中;否则找到属于第二部分对用的层级中。
// skynet_timer.c
void
skynet_updatetime(void) {
...
uint32_t diff = (uint32_t)(cp - TI->current_point);
TI->current_point = cp;
TI->current += diff;
// diff单位为0.01s
for (i = 0; i < diff; i++){
timer_update(TI);
}
}
static void
timer_update(struct timer *T) {
SPIN_LOCK(T);
timer_execute(T); // 检查T->near是否位空,有就处理到期定时器
timer_shift(T); // 时间片time++,移动高24位的链表
timer_execute(T);
SPIN_UNLOCK(T);
}
// 每帧从T->near中触发到期得定时器
static inline void
timer_execute(struct timer *T) {
...
}
// 遍历处理定时器链表中所有的定时器
static inline void
dispatch_list(struct timer_node *current) {
...
}
// 将高24位对应的4个6位的数组中的各个元素的链表往低位移
static void
timer_shift(struct timer *T) {
...
}
// 将level层的idx位置的定时器链表从当前位置删除,并重新add_node
static void move_list(struct timer *T, int level, int idx) {
}
最小堆实现定时器
最小堆实现例子:boost.asio
采用二叉树,go
采用四叉树, libuv
具体实现略。
总结
本文主要介绍定时器作用,实现定时器数据结构选取,并详细介绍了跳表,红黑树,时间轮实现定时器的思路和方法。
Python人工智能编程分享 Python 相关的技术文章、工具资料、视频教程等。专注Python编程开发学习以及人工智能、机器学习、自然语言处理、深度学习、图像识别、语音识别、无人驾驶等前沿AI技术学习!
公众号
-
Linux
+关注
关注
87文章
11304浏览量
209497 -
定时器
+关注
关注
23文章
3248浏览量
114800 -
数据结构
+关注
关注
3文章
573浏览量
40130
发布评论请先 登录
相关推荐
评论