Timer 线程详解目录概述主要职责定时器数据结构最小堆实现代码实现工作流程SIGHUP 信号处理性能优化概述Timer 线程是 Skynet 的定时器管理线程负责管理所有定时器、触发超时事件、更新系统时间并处理日志轮转信号。Timer 线程通过分层时间轮数据结构高效管理定时器保证系统定时任务的精确执行。特点:使用分层时间轮管理定时器插入和触发的时间复杂度为 O(1)每隔 2.5 毫秒更新一次系统时间支持高精度定时厘秒级别处理 SIGHUP 信号实现日志轮转文件位置:skynet/skynet-src/skynet_timer.c线程数量: 1 个主要职责1. 定时器管理功能: 管理所有服务的定时器操作:创建定时器 (skynet_timeout)删除定时器 (skynet_timer_remove)更新定时器触发到期的定时器2. 超时事件触发功能: 当定时器到期时发送消息到对应服务触发方式:// 发送 PTYPE_RESPONSE 消息到服务skynet_send(NULL,0,handle,PTYPE_RESPONSE,session,NULL,0);3. 系统时间更新功能: 更新 Skynet 的系统时间时间精度: 厘秒1/100 秒函数:skynet_updatetime()4. SIGHUP 信号处理功能: 处理日志轮转信号触发方式: 用户发送 SIGHUP 信号给进程处理方式: 发送 PTYPE_SYSTEM 消息给 Logger 服务定时器数据结构时间轮核心结构文件:skynet/skynet-src/skynet_timer.c// 定时器节点structtimer_node{structtimer_node*next;/* 链表下一节点 */uint32_texpire;/* 过期时间绝对 ticks */};// 定时器事件structtimer_event{uint32_thandle;/* 目标服务句柄 */intsession;/* 会话ID用于匹配请求-响应 */};// 定时器链表每个槽位structlink_list{structtimer_nodehead;/* 头节点哨兵 */structtimer_node*tail;/* 尾指针用于快速追加 */};// 定时器主结构structtimer{structlink_listnear[TIME_NEAR];/* 近期时间轮256个槽位 */structlink_listt[4][TIME_LEVEL];/* 4层时间轮每层64个槽位 */structspinlocklock;/* 自旋锁保护并发访问 */uint32_ttime;/* 当前逻辑时间ticks会回绕 */uint32_tstarttime;/* skynet 启动时的系统时间秒 */uint64_tcurrent;/* 累计逻辑时间ticks不回绕 */uint64_tcurrent_point;/* 上次更新的物理时间ticks */};时间轮层级说明时间轮结构: near[256] - 近期时间轮0-255 ticks约 0-2.55 秒 │ ├── [0] → link_list → node → node → ... ├── [1] → link_list → node → ... └── ... t[4][64] - 4层时间轮 │ ├── t[0][64] - 第0层256-16383 ticks约 2.56-163.83 秒 ├── t[1][64] - 第1层16384-1048575 ticks约 2.7-174 分钟 ├── t[2][64] - 第2层1048576-67108863 ticks约 2.9-186 小时 └── t[3][64] - 第3层67108864-4294967295 ticks约 18.6-1193 小时字段说明字段类型说明nexttimer_node*链表下一节点指针expireuint32_t过期时间绝对tickshandleuint32_t目标服务句柄sessionint会话 ID用于关联请求和响应near[256]link_list近期时间轮256个槽位t[4][64]link_list4层时间轮每层64个槽位timeuint32_t当前逻辑时间会回绕currentuint64_t累计逻辑时间不回绕时间轮实现时间轮原理分层时间轮是一种高效的定时器管理数据结构通过分层设计支持大范围时间。时间轮示例near层级: 当前时间: tick100 near[100] → [任务A] → [任务B] → NULL near[101] → [任务C] → NULL near[102] → NULL ... 特点 1. 每个槽位对应一个时间点 2. 槽位中的任务按链表存储 3. 时间推进时处理当前槽位的任务 4. 高层任务逐步降级到低层时间轮操作1. 添加定时器添加步骤 1. 计算过期时间: expire current_time delay 2. 根据过期时间选择层级: - 差值 255: 放入 near[expire 0xFF] - 差值 16383: 放入 t[0] - 差值 1048575: 放入 t[1] - ... 3. 追加到对应槽位的链表末尾 时间复杂度O(1)代码实现:staticvoidadd_node(structtimer*T,structtimer_node*node){uint32_ttimenode-expire;uint32_tcurrent_timeT-time;// 检查是否属于 near 层级差值在0-255内if((time|TIME_NEAR_MASK)(current_time|TIME_NEAR_MASK)){// 放入 near 数组link(T-near[timeTIME_NEAR_MASK],node);}else{// 需要放入更高层级inti;uint32_tmaskTIME_NEARTIME_LEVEL_SHIFT;for(i0;i4;i){if((time|(mask-1))(current_time|(mask-1))){break;}maskTIME_LEVEL_SHIFT;}// 放入对应的层级link(T-t[i][((time(TIME_NEAR_SHIFTi*TIME_LEVEL_SHIFT))TIME_LEVEL_MASK)],node);}}2. 触发定时器触发步骤 1. 检查 near[time 0xFF] 槽位 2. 取出链表中所有节点 3. 向每个目标服务发送消息 4. 释放节点内存 时间复杂度O(1)均摊代码实现:staticinlinevoiddispatch_list(structtimer_node*current){do{structtimer_event*event(structtimer_event*)(current1);structskynet_messagemessage;message.source0;message.sessionevent-session;message.dataNULL;message.sz(size_t)PTYPE_RESPONSEMESSAGE_TYPE_SHIFT;skynet_context_push(event-handle,message);structtimer_node*tempcurrent;currentcurrent-next;skynet_free(temp);}while(current);}3. 时间推进与层级降级推进步骤 1. timer_execute() - 触发当前槽位的任务 2. timer_shift() - 时间1检查是否有进位 3. 如果有进位 - 从高层级取出任务重新add_node到低层级 4. timer_execute() - 再次触发可能有新降级的任务 时间复杂度O(1)均摊代码实现Timer 线程主函数文件:skynet/skynet-src/skynet_start.c位置:skynet_start.c:265-298staticvoid*thread_timer(void*p){structmonitor*mp;skynet_initthread(THREAD_TIMER);for(;;){// 1. 更新定时器触发到期的定时器skynet_updatetime();// 2. 更新 socket 时间skynet_socket_updatetime();// 3. 检查是否应该退出CHECK_ABORT// 4. 唤醒 worker至少保留一个 worker 工作wakeup(m,m-count-1);// 5. 休眠 2.5 毫秒usleep(2500);// 6. 处理 SIGHUP 信号if(SIG){signal_hup();// 发送重新打开日志的消息SIG0;}}// 退出时的清理工作// 1. 唤醒 socket 线程skynet_socket_exit();// 2. 唤醒所有 worker 线程pthread_mutex_lock(m-mutex);m-quit1;pthread_cond_broadcast(m-cond);pthread_mutex_unlock(m-mutex);returnNULL;}更新定时器文件:skynet/skynet-src/skynet_timer.cvoidskynet_updatetime(void){// 获取当前时间厘秒uint64_tnowskynet_thread_time()/10000;// 更新系统时间TI-current_pointnow;// 处理到期的定时器dispatch_timer(now);}触发到期定时器文件:skynet/skynet-src/skynet_timer.cstaticvoidtimer_execute(structtimer*T){// 获取当前槽位索引intidxT-timeTIME_NEAR_MASK;// 如果槽位不为空取出所有节点structtimer_node*currentlink_clear(T-near[idx]);// 分发所有到期任务if(current){dispatch_list(current);}}staticinlinevoiddispatch_list(structtimer_node*current){do{structtimer_event*event(structtimer_event*)(current1);structskynet_messagemessage;message.source0;message.sessionevent-session;message.dataNULL;message.sz(size_t)PTYPE_RESPONSEMESSAGE_TYPE_SHIFT;// 发送消息到目标服务skynet_context_push(event-handle,message);// 释放节点structtimer_node*tempcurrent;currentcurrent-next;skynet_free(temp);}while(current);}创建定时器文件:skynet/skynet-src/skynet_timer.cintskynet_timeout(uint32_thandle,inttime,intsession){if(time0){// 立即触发structskynet_messagemessage;message.source0;message.sessionsession;message.dataNULL;message.sz(size_t)PTYPE_RESPONSEMESSAGE_TYPE_SHIFT;skynet_context_push(handle,message);returnsession;}// 创建定时器事件structtimer_eventevent;event.handlehandle;event.sessionsession;// 添加到时间轮timer_add(TI,event,sizeof(event),time);returnsession;}staticvoidtimer_add(structtimer*T,void*arg,size_tsz,inttime){// 分配节点内存包含timer_node和timer_eventstructtimer_node*node(structtimer_node*)skynet_malloc(sizeof(*node)sz);// 复制事件数据memcpy(node1,arg,sz);// 加锁SPIN_LOCK(T);// 计算过期时间node-expiretimeT-time;// 添加到时间轮add_node(T,node);// 解锁SPIN_UNLOCK(T);}工作流程┌─────────────────────────────────────────────────────────────┐ │ Timer 线程工作流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 线程启动 │ │ │ │ │ ▼ │ │ 初始化线程局部存储 │ │ skynet_initthread(THREAD_TIMER) │ │ │ │ │ ▼ │ │ 主循环 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ while (!quit) { │ │ │ │ │ │ │ │ │ ├─ skynet_updatetime() │ │ │ │ │ │ │ │ │ │ │ ├─ 获取当前时间 │ │ │ │ │ ├─ 更新系统时间 │ │ │ │ │ └─ dispatch_timer() │ │ │ │ │ │ │ │ │ │ │ ├─ 检查堆顶定时器 │ │ │ │ │ ├─ if (到期) │ │ │ │ │ │ ├─ 发送消息到服务 │ │ │ │ │ │ ├─ 删除堆顶定时器 │ │ │ │ │ │ └─ 释放节点 │ │ │ │ │ └─ continue │ │ │ │ │ │ │ │ │ ├─ skynet_socket_updatetime() │ │ │ │ │ 更新 socket 时间 │ │ │ │ │ │ │ │ │ ├─ CHECK_ABORT │ │ │ │ │ (检查是否应该退出) │ │ │ │ │ │ │ │ │ ├─ wakeup(m, m-count-1) │ │ │ │ │ 唤醒 worker 线程 │ │ │ │ │ │ │ │ │ ├─ usleep(2500) │ │ │ │ │ 休眠 2.5 毫秒 │ │ │ │ │ │ │ │ │ ├─ if (SIG) │ │ │ │ │ signal_hup() │ │ │ │ │ 处理日志轮转 │ │ │ │ │ │ │ │ │ └─ 继续循环 │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ▼ │ │ 退出时清理 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ skynet_socket_exit() │ │ │ │ 唤醒 socket 线程退出 │ │ │ │ │ │ │ │ m-quit 1 │ │ │ │ pthread_cond_broadcast(m-cond) │ │ │ │ 唤醒所有 worker 线程退出 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ▼ │ │ return NULL │ │ │ └─────────────────────────────────────────────────────────────┘SIGHUP 信号处理信号注册文件:skynet/skynet-src/skynet_start.cstaticvolatileintSIG0;staticvoidhandle_hup(intsignal){if(signalSIGHUP){SIG1;}}// 在 skynet_start() 中注册信号structsigactionsa;sa.sa_handlerhandle_hup;sa.sa_flagsSA_RESTART;sigfillset(sa.sa_mask);sigaction(SIGHUP,sa,NULL);信号处理文件:skynet/skynet-src/skynet_start.cstaticvoidsignal_hup(){// 创建系统消息structskynet_messagesmsg;smsg.source0;smsg.session0;smsg.dataNULL;smsg.sz(size_t)PTYPE_SYSTEMMESSAGE_TYPE_SHIFT;// 发送消息给 logger 服务uint32_tloggerskynet_handle_findname(logger);if(logger){skynet_context_push(logger,smsg);}}日志轮转流程用户发送 SIGHUP 信号 │ ▼ 进程收到信号 │ ▼ handle_hup() 处理 │ ├─ SIG 1 │ ▼ Timer 线程循环 │ ├─ 检测到 SIG 1 │ ▼ signal_hup() │ ├─ 查找 logger 服务 ├─ 创建 PTYPE_SYSTEM 消息 └─ 发送消息到 logger │ ▼ logger 服务收到消息 │ ▼ logger_cb() 处理 │ ├─ if (type PTYPE_SYSTEM) ├─ inst-handle freopen(inst-filename, a, inst-handle) └─ 重新打开日志文件性能优化1. 时间轮优化优势:插入定时器O(1)触发定时器O(1)均摊内存占用固定与定时器数量无关实现细节:分层设计支持大范围时间0-1193小时链表追加O(1)时间复杂度批量触发一次处理一个槽位的所有任务层级降级分摊到多次时间推进中2. 批量处理机制: 一次处理一个槽位的所有定时器staticvoidtimer_execute(structtimer*T){intidxT-timeTIME_NEAR_MASK;structtimer_node*currentlink_clear(T-near[idx]);if(current){dispatch_list(current);// 批量处理}}3. 时间精度控制循环间隔: 2.5 毫秒原因:定时器精度为厘秒10 毫秒2.5 毫秒足够响应定时器减少不必要的唤醒修改建议:需要更高精度时可以减少usleep时间但会增加 CPU 占用定时器使用示例Lua 层定时器localskynetrequireskynetskynet.start(function()-- 1 秒后执行skynet.timeout(100,function()print(1 秒后执行)end)-- 重复定时器使用协程localfunctionloop()whiletruedoprint(每秒执行一次)skynet.sleep(100)-- 1 秒 100 厘秒endendskynet.fork(loop)end)C 层定时器// 创建定时器intsessionskynet_context_newsession(ctx);skynet_timeout(ctx-handle,100,session);// 1 秒后触发// 在回调中接收voidcallback(structskynet_context*context,void*ud,inttype,intsession,uint32_tsource,constvoid*msg,size_tsz){if(typePTYPE_RESPONSE){// 定时器到期printf(Timer triggered, session%d\n,session);}}配置和调优默认配置配置项默认值说明循环间隔2.5 毫秒Timer 线程的循环间隔定时器精度10 毫秒定时器的时间精度堆初始容量32定时器堆的初始容量调优建议1. 提高定时器精度// 原代码usleep(2500);usleep(1000);// 改为 1 毫秒效果: 定时器响应更快但 CPU 占用增加2. 增加堆容量// 原代码TI-cap 32;TI-cap128;// 增加初始容量效果: 减少扩容次数提高性能常见问题Q1: 定时器精度如何答案: Skynet 的定时器精度为厘秒10 毫秒。Q2: 可以创建多个定时器吗答案: 可以每个服务可以创建多个定时器。Q3: 定时器会被垃圾回收吗答案: 不会定时器到期后会自动释放。Q4: 如何取消定时器答案: 定时器触发后自动取消。如果需要提前取消可以在回调中检查条件。总结Timer 线程的核心作用:定时器管理: 通过分层时间轮高效管理所有定时器超时触发: 精确触发定时器到期事件时间更新: 维护系统时间日志轮转: 处理 SIGHUP 信号关键机制:分层时间轮: 高效管理定时器O(1)插入和触发厘秒精度: 精确的时间控制10ms信号处理: 支持日志轮转性能特点:插入定时器O(1)触发定时器O(1)均摊内存占用固定开销512个槽位与其他线程的关系:Worker 线程接收定时器消息主线程创建 Timer 线程Socket 线程更新 socket 时间