第 41 章 / 共 51 章
卷五 · 扩展、运维与实战
定时器与后台任务:时间维度的脚本
请求之外的两种执行时机:短平快的定时回调(宿主托管)与长阻塞的后台工作(自己开线程)。分界线画在哪是本章的主题。
本章导读
站位:工具章。两个 API(xsTimerAfter/Cancel)加一个线程模型问题。ch27 的文件监听、ch22 的心跳看门狗在这里得到统一的时间机制。
41.1 引入
服务的"时间维度"需求很朴素:过期清理、周期统计、延迟重试、超时剔除。XServer 的答案是一个一次性定时器——刻意不提供周期版本(见 41.3 的重排模式),因为周期语义与代际生命周期有微妙的交互,显式重排让每一步都可控。定时器绑定创建它的代(ch30 讲过作废语义),回调在引擎线程执行——可以安全调用宿主 API。
41.2 定时器与代
基本用法与代绑定
static void OnCleanup(void* pUserData) { ... }
/* ServiceInit 里:30 秒后触发一次 */
uint64 iTimer = xsTimerAfter(pHost, 30000, OnCleanup, NULL);
/* 返回 0 = 失败;非零 ID 可 Cancel */
/* 代绑定三律:*/
/* 1. 创建线程的"当前代"被捕获——回调访问的静态变量必属该代 */
/* 2. 换代 → 旧代定时器全部作废(不会打进新代) */
/* 3. 新代要继续 → Init 里重新挂(周期任务因此显式化) */
这个设计消灭了一整类 bug:"旧代的定时器摸了新代的静态变量"。代价是你要接受"定时器不跨代"——而跨代延续本就该走 Swap(ch28)。
41.3 周期任务模式
自重排周期任务
static XS_HostInfo* g_pHostForTick; /* Init 记录(回调拿不到 host)*/
static void OnTick(void* pUserData)
{
DoPeriodicWork(); /* 快活:统计/清理/探测 */
/* 重排下一次(当前代仍活着才能挂上) */
(void)xsTimerAfter(g_pHostForTick, 5000, OnTick, NULL);
}
/* ServiceInit:g_pHostForTick = pHost; xsTimerAfter(pHost, 5000, OnTick, NULL); */
自重排的好处全在"换代自动停":旧代最后一次触发后不再重排(新代 Init 挂新的),任务天然跟着代走——不需要取消逻辑。注意重排放在任务逻辑之后:任务抛错时你可以选择不再重排(停摆也是一种策略)。
41.4 后台线程边界
定时回调跑在引擎线程——必须短(第 3 章契约:拖住 worker 就是拖住所有请求)。长活(批量计算、外部调用、大文件处理)放独立线程,然后面对经典问题:后台线程能不能调宿主/xrt API?
| 操作 | 后台线程可用性 | 模式 |
|---|---|---|
| xrt 通用计算(容器/字符串/JSON) | ✓ 可用 | 库级 API 自带线程语义 |
| 文件 / 数据库(SQLite) | ✓ 可用 | 句柄自己管(FULLMUTEX 等) |
| 连接对象收发 | ✗ 常规模式禁止 | 连接归驱动事件循环——后台想发消息走"投递回引擎"结构 |
| 回调视图 / 请求对象 | ✗ 禁止 | 回调栈外即无效(第 3 章铁律) |
| XServer 层 API(reload/timer/topo) | Submit/Query ✓ | 设计为非阻塞;拓扑查询按 lease 纪律 |
标准结构是任务队列:后台线程做重活,完成后把结果投递回引擎(定时器轮询结果或队列唤醒),由引擎线程碰连接与响应——方向永远清晰:重活出去、结果回来、IO 归引擎。
41.5 契约
- 短快线:定时回调短平快;长活进后台线程 + 结果投递。
- 代际线:定时器不跨代;周期任务自重排;延续状态走 Swap。
- IO 线:连接与视图只在引擎线程碰;后台线程经任务队列交接。
常见错误
定时回调里做外部调用
- 症状
- 定时任务一跑,全服请求延迟飙升。
- 原因
- 回调占了 worker——外部 HTTP 调用动辄秒级。挪后台线程,定时器只负责"触发"。
后台线程直接回了响应
- 症状
- 偶发响应错乱 / 崩溃。
- 原因
- 请求已结束(回调返回了),后台还拿着视图指针在写。后台完成的是"数据",响应由引擎侧的投递处理发出。
编程练习
基础过期清理器
每 5 秒清理注册表中 60 秒无活动的会话(自重排模式)。
验收:注入僵尸会话被清;重载后清理器继续(新代 Init 重建)。
进阶异步聚合接口
实现 /stats/slow:后台线程聚合 1 万行数据,完成后可查询(任务队列 + 定时器轮询完成位)。
验收:聚合期间请求延迟不受影响;结果最终可见。
挑战换代压力下的时间任务
周期任务运行中连续换代 10 次,验证任务跟随当前代、无旧代触发。
验收:日志中每个代的任务只在自己生命周期内触发。
小结
| 要点 | 结论 |
|---|---|
| 定时器 | 一次性、绑定创建代、回调在引擎线程 |
| 周期 | 自重排模式——换代自动停、无需取消逻辑 |
| 分界线 | 短快进回调;长活进后台线程 + 结果投递 |
| IO 归属 | 连接/视图只在引擎线程;后台经任务队列交接 |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · thread — 后台线程创建与任务队列材料
- xrt 参考 · time — 高精度时钟(任务耗时测量)