实现方案分层时间轮精度10ms可配置通常为 10ms层级共 5 层near T[0] ~ T[3]秒针near 层每 tick 移动一格走完一圈256 ticks 2.56s。分针T[0] 层秒针每走一圈分针移动一格共 64 格64 × 256 ticks ≈ 164s。时针T[1] 层分针每走一圈时针移动一格共 64 格约 2.9 小时。天/月T[2]、T[3] 层依次类推最大可支持约 136 年理论值。槽数near 层 256 个槽其余每层 64 个槽数据结构structtimer{structlink_listnear[TIME_NEAR];// 近期槽256个structlink_listt[4][TIME_LEVEL];// 远期槽4层×64个uint32_tcurrent;// 当前时间戳单位10ms...};核心接口计算相对偏移staticvoidtimer_add(structtimer*T,void*arg,size_tsz,inttime){// 1. 分配节点内存包含用户数据structtimer_node*node(structtimer_node*)skynet_malloc(sizeof(*node)sz);memcpy(node1,arg,sz);// 用户数据放在节点后面// 2. 计算绝对过期时间node-expireT-currenttime;// 3. 添加到时间轮add_node(T,node);}核心逻辑#defineTIME_NEAR256// 2^8#defineTIME_NEAR_SHIFT8// log2(TIME_NEAR)#defineTIME_LEVEL64// 2^6#defineTIME_LEVEL_SHIFT6// log2(TIME_LEVEL)staticvoidadd_node(structtimer*T,structtimer_node*node){uint32_tdiffnode-expire-T-current;// 距离当前还有多少 tickuint32_tcurrentT-current;if(diffTIME_NEAR){// [0, 255]// 放入 near 层槽索引 expire (TIME_NEAR-1)intidxnode-expire(TIME_NEAR-1);link_head(T-near[idx],node);}elseif(diff(1(TIME_NEAR_SHIFTTIME_LEVEL_SHIFT))){// 放入 t[0] 层256 ~ 16383intidx(node-expireTIME_NEAR_SHIFT)(TIME_LEVEL-1);link_head(T-t[0][idx],node);}elseif(diff(1(TIME_NEAR_SHIFT2*TIME_LEVEL_SHIFT))){// 放入 t[1] 层intidx(node-expire(TIME_NEAR_SHIFTTIME_LEVEL_SHIFT))(TIME_LEVEL-1);link_head(T-t[1][idx],node);}elseif(diff(1(TIME_NEAR_SHIFT3*TIME_LEVEL_SHIFT))){// 放入 t[2] 层intidx(node-expire(TIME_NEAR_SHIFT2*TIME_LEVEL_SHIFT))(TIME_LEVEL-1);link_head(T-t[2][idx],node);}else{// 放入 t[3] 层intidx(node-expire(TIME_NEAR_SHIFT3*TIME_LEVEL_SHIFT))(TIME_LEVEL-1);link_head(T-t[3][idx],node);}}staticinlinevoidlink_head(structtimer_node**list,structtimer_node*node){node-next*list;*listnode;}TIME_NEAR指定为256的原因必须是 2 的幂以便用位运算 (TIME_NEAR-1)代替取模 % 256。覆盖时间范围256 tick × 10ms/tick 2.56 秒足以容纳大多数短期定时器如技能冷却、心跳。256 是 8 位二进制能表示的最大值便于后续移位操作。TIME_NEAR_SHIFT 8:用于将 expire右移 8 位以提取高于 near 层的部分。TIME_LEVEL 64定义高层t[0] ~ t[3]每层的槽数量。64 是合理的折中每层覆盖上一层的 64 倍范围且槽总数不多4层×64256个槽内存占用小。配合 TIME_NEAR使得层级间呈指数增长256 → 256×64 → 256×64² →TIME_LEVEL_SHIFT 6用于在高层之间进行移位以确定定时器属于哪一层、哪一个槽。t[0] 层 (TIME_NEAR_SHIFT) 8t[1] 层 (TIME_NEAR_SHIFT TIME_LEVEL_SHIFT) 14t[2] 层 (TIME_NEAR_SHIFT 2*TIME_LEVEL_SHIFT) 20t[3] 层 (TIME_NEAR_SHIFT 3*TIME_LEVEL_SHIFT) 26定时器触发voidtimer_update(structtimer*T){// 1. 当前 tick 自增T-current;// 2. 检查 near 层指针是否回绕到 0// current 的低 8 位就是 near 层当前槽索引uint32_tmaskTIME_NEAR-1;if((T-currentmask)0){// near 层走完一圈需要从高层迁移一批定时器timer_move(T,0);// 从 t[0] 层迁移到 near 层}// 3. 处理 near 层当前槽的所有定时器intidxT-currentmask;structtimer_node*nodeT-near[idx];T-near[idx]NULL;// 清空槽dispatch_list(T,node);// 执行回调}staticvoidtimer_move(structtimer*T,intlevel){// 计算当前层应该处理的槽索引uint32_tshiftTIME_NEAR_SHIFTlevel*TIME_LEVEL_SHIFT;intidx(T-currentshift)(TIME_LEVEL-1);// 取出该槽的所有节点structtimer_node*nodeT-t[level][idx];T-t[level][idx]NULL;// 遍历链表对每个节点重新调用 add_node再次计算 diffwhile(node){structtimer_node*nextnode-next;add_node(T,node);// 递归插入到更低的层级nodenext;}// 如果该层指针也回绕即 idx0则需要向更高层迁移if(idx0){timer_move(T,level1);}}staticvoiddispatch_list(structtimer*T,structtimer_node*node){while(node){structtimer_node*nextnode-next;// 从节点中取出用户数据调用回调函数structtimer_event*event(structtimer_event*)(node1);event-func(event-data);skynet_free(node);// 释放节点nodenext;}}当 near 层指针回绕即 current的低 8 位变为 0时说明近期的 256 个 tick 已经全部处理完毕需要从更高层取出下一批定时器重新分布到 near 层。不同方案对比为何时间轮不会带来较大负担分摊处理每次 tick 只处理一个槽而不是扫描所有定时器。分层迁移长超时的定时器被“雪藏”在高层级只有在其到期前的几个 tick 才会被逐步迁移到 near 层避免了频繁检查。无锁或少锁skynet 的定时器在主线程worker 线程中执行天然串行无需加锁。内存局部性好槽链表连续CPU 缓存友好。使用建议精度与实时性限制粒度固定Skynet 精度为 10ms无法满足亚毫秒级需求如音视频同步。抖动如果某个 tick 的回调执行时间超过 10ms会导致后续 tick 延迟形成累积效应。解决方案将耗时任务异步化投递到独立服务不要在定时器回调中做繁重计算。定时器堆积当大量定时器同时到期如整点刷新、全服活动开始同一槽内的链表可能非常长导致该 tick 的处理时间飙升引发“惊群”现象。应对策略引入随机偏移jitter让定时器分散在不同 tick。对密集事件做采样合并如每秒更新一次而非每帧更新。时钟回拨如果系统时间被人为调整或 NTP 校正导致时间倒退current会突然减小导致已过期的定时器被误认为在未来造成永久丢失。解决方案使用单调时钟CLOCK_MONOTONIC代替墙上时间。Skynet 正是如此它维护自增的 current不受系统时间影响。若必须使用墙上时间检测到回拨时应立即触发所有已过期的定时器。