采用时间轮方式,哈希表+链表实现,
structtimer_node { //时间节点structtimer_node *next; uint32_t 过期; //过期的tick数};struct link_list { //链表struct timer_node head; structtimer_node *tail;};structtimer { structlink_listnear[256]; //即将到来的定时器struct link_list t[4][64]; //相对较远的计时器struct spinlock lock; uint32_t 时间; //记录当前的刻度数uint64_t starttime; uint64_t 当前; uint64_t current_point;};其中Time是一个32位无符号整数,记录时间片对应数组near[256],代表即将到来的定时任务,t[4][64],代表更远的定时任务。
天网时间轮
t[3]t[2]t[1]t[0]near26-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时间和高24位时间是否相等。如果相等,则将该节点添加到expire的低8位对应的near数组元素的链表中。如果不相等,则继续下一步检查expire和time的高18位是否相等。如果相等,则将该节点添加到expire的低9到14位对应的6位二进制值对应的数组t[0]的元素中。链表中,否则进入下一步,检查expire的高12位与time是否相等。如果相等,则将该节点添加到expire的低15到20位对应的数组t[1]的元素对应的6位二进制值的链表中。如果不相等,则继续下一步检查expire和time的高6位是否相等。如果相等,则将该节点添加到expire的低21到26位对应的6位二进制值对应的数组t[2]的元素中。链表中,如果不相等,则进行下一步,将该节点添加到低27~32位对应的6位二进制值对应的数组t[3]的元素链表中的过期。以下是具体实现,仅贴出Main界面,具体细节请参考skynet源码。
//skynet_start.c //skynet启动入口voidskynet_start(struct skynet_config * config) { . skynet_timer_init();}//skynet_timer.cvoidskynet_timer_init(void) { //创建全局定时器结构体TI TI=timer_create_timer(); uint32_t 当前=0; systime(TI-启动时间,当前); TI-电流=电流; TI-current_point=gettime();}
通过skynet_server.c中的cmd_timeout调用skynet_timeout添加一个新的定时器
//TI 是全局定时器指针static structtimer * TI=NULL; int skynet_timeout(uint32_t 句柄, int 时间, int 会话) { . struct timer_event 事件; event.handle=句柄; //回调Even.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指针分配空间,还需要分配大小为计时器节点+计时器事件。 //之后可以通过node + 1获取timer_event数据。 struct timer_node *node=(struct timer_node *)skynet_malloc(sizeof(*node )+sz); memcpy(节点+1,arg,sz);自旋锁(T);节点过期=时间+T时间; add_node(T, 节点); SPIN_UNLOCK(T);}//添加到定时器链表中,如果定时器的到期tick数与当前比较接近(2^8),则表示触发的定时器将被添加到T-near数组中//否则,会根据差值添加到对应的T-T[i]中static void add_node(struct timer *T, struct timer_node *node) { .}
skynet启动时,会创建一个线程专门用于运行计时器,并且skynet_updatetime() 将在每帧(0.0025s)被调用
//skynet_start.cstatic void * thread_timer(void *p) { struct monitor * m=p; skynet_initthread(THREAD_TIMER); for (;) { skynet_updatetime(); //调用timer_update skynet_socket_updatetime(); CHECK_ABORT 唤醒(m,m -count-1);我们睡觉(2500); //2500 微秒=0.0025 秒if (SIG) { signal_hup(); SIG=0;每个定时器设置一个到期tick数,与当前系统的tick数(启动时0,1tick和1tick向后跳转,1tick==0.01s)进行比较,得到差值间隔;
如果interval比较小(0=interval=2^8-1),则说明定时器即将到来,存放在第一部分的第一个2^8定时器链表中;否则,找到属于该对的第二部分的级别。
//skynet_timer.cvoid skynet_updatetime(void) { . uint32_t diff=(uint32_t)(cp - TI-current_point); TI-当前点=cp; TI-电流+=差异; //diff 单位为0.01s for (i=0; i diff; i++){ timer_update(TI); }}静态voidtimer_update(结构定时器* T){ SPIN_LOCK(T);定时器执行(T); //检查T-near是否为空,如果为Timer则处理过期timer_shift(T); //时间片time++,移动高24位链表timer_execute(T); SPIN_UNLOCK(T);}//每帧附近从T开始触发过期定时器static inline voidtimer_execute(struct timer *T) { .}//遍历定时器链表中所有定时器static inline voiddispatch_list(struct timer_node *current ) { .}//将四个6 位数组对应的高24 位的链表移至低位static voidtimer_shift(struct timer *T) { .}//删除定时器从当前位置到level层的idx位置的链表,并重新add_nodestatic void move_list(struct timer *T, int level, int idx) {}
最小堆实现示例:boost.asio使用二叉树, go 使用四叉树、libuv
具体实现不再赘述。
本文主要介绍定时器的功能,实现定时器数据结构的选择,详细介绍了通过跳表、红黑树、时间轮实现定时器的思路和方法。
Python人工智能编程分享Python相关技术文章、工具资料、视频教程等。专注学习Python编程开发以及人工智能、机器学习、自然语言处理、深度学习、图像识别、语音等前沿AI技术识别、无人驾驶!
没有公开