skynet Monitor 线程详解
目录
- 概述
- 主要职责
- 死循环检测原理
- 代码实现
- 工作流程
- 监控器数据结构
- 与 Worker 线程的交互
概述
Monitor 线程是 Skynet 的监控线程,负责监控所有 Worker 线程的运行状态,检测服务是否陷入死循环。Monitor 线程通过定时检查每个 Worker 的执行状态,发现异常时记录日志,帮助开发者定位问题。
特点:
- 独立线程运行,不参与消息处理
- 每隔约 5 秒检查一次所有 Worker
- 通过版本号机制检测死循环
- 记录死循环服务的详细信息
文件位置:skynet/skynet-src/skynet_start.c
线程数量: 1 个
主要职责
1. 监控 Worker 线程
功能: 检查每个 Worker 线程是否正常工作
检查内容:
- Worker 是否在处理消息
- Worker 是否长时间未更新状态
- Worker 处理的消息是否完成
2. 死循环检测
功能: 检测服务是否陷入死循环
检测机制:
- Worker 每处理一条消息,版本号递增
- Monitor 定期检查版本号是否变化
- 如果 5 秒内版本号未变化,判定为死循环
- 记录死循环服务的日志信息
3. 运行时间统计
功能: 统计服务的运行时间
统计内容:
- 服务处理消息的总时间
- 服务最后活跃时间
- 服务是否超时
死循环检测原理
版本号机制
┌─────────────────────────────────────────────────────────────┐ │ 死循环检测原理 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Worker 线程处理消息时: │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 1. 从消息队列取出消息 │ │ │ │ 2. 更新监控器: version++ │ │ │ │ 3. 记录开始时间: start_time = now │ │ │ │ 4. 调用服务回调函数 │ │ │ │ 5. 更新监控器: version++ │ │ │ │ 6. 记录结束时间: end_time = now │ │ │ │ 7. 清空监控器 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ Monitor 线程检查时: │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 1. 遍历所有 Worker 的监控器 │ │ │ │ 2. 检查 version 是否变化 │ │ │ │ 3. 检查 start_time 是否超时 │ │ │ │ 4. 如果 5 秒内 version 未变化 → 死循环 │ │ │ │ 5. 如果 5 秒内 start_time 未结束 → 超时 │ │ │ │ 6. 记录日志信息 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘检测流程
时间轴: T=0s T=1s T=2s T=3s T=4s T=5s │ │ │ │ │ │ ├───────┼───────┼───────┼───────┼───────┤ │ │ │ │ │ │ Monitor │ │ │ │ │ 检查 │ │ │ │ │ version=1 │ │ │ │ │ │ │ │ Worker │ │ │ 处理消息1 │ │ │ version=2 │ │ │ │ │ │ Worker │ │ 处理消息2 │ │ version=3 │ │ │ │ Worker │ 处理消息3 │ version=4 │ │ Monitor 检查 version=4 正常 ✓死循环场景
T=0s T=1s T=2s T=3s T=4s T=5s │ │ │ │ │ │ ├───────┼───────┼───────┼───────┼───────┤ │ │ │ │ │ │ Monitor │ │ │ │ │ 检查 │ │ │ │ │ version=1 │ │ │ │ │ │ │ │ Worker │ │ │ 处理消息1 │ │ │ version=2 │ │ │ │ │ │ Worker │ │ 死循环! │ │ version=2 │ │ (未变化) │ (未变化) │ (未变化) │ │ Monitor 检查 version=2 死循环 ✗代码实现
Monitor 线程主函数
文件:skynet/skynet-src/skynet_start.c
位置:skynet_start.c:202-226
staticvoid*thread_monitor(void*p){structmonitor*m=p;inti;intn=m->count;// Worker 线程数量skynet_initthread(THREAD_MONITOR);pthread_setname_np(pthread_self(),"monitor");// 设置线程名称for(;;){CHECK_ABORT// 检查是否应该退出// 遍历所有 worker 的监控器for(i=0;i<n;i++){skynet_monitor_check(m->m[i]);// 检查监控器}// 休眠 5 秒(分成 5 次,每次 1 秒,以便快速响应退出)for(i=0;i<5;i++){CHECK_ABORT// 检查是否应该退出sleep(1);// 休眠 1 秒}}returnNULL;}监控器检查函数
文件:skynet/skynet-src/skynet_monitor.c
voidskynet_monitor_check(structskynet_monitor*sm){inti;for(i=0;i<5;i++){// 检查每个 workeruint32_tv=sm->version[i];// 读取版本号uint32_thandle=sm->source[i];// 读取服务句柄if(v!=sm->check_version[i]){// 版本号变化了,更新检查版本号sm->check_version[i]=v;sm->check_source[i]=handle;continue;}if(handle){// 版本号未变化,但 handle 不为空// 说明服务还在处理消息uint32_thandle=sm->source[i];uint32_thandle_s=sm->check_source[i];if(handle_s!=0&&handle==handle_s){// 同一个服务 5 秒内版本号未变化// 判定为死循环fprintf(stderr,"Deadloop detected in service %d\n",handle);}}}}工作流程
┌─────────────────────────────────────────────────────────────┐ │ Monitor 线程工作流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 线程启动 │ │ │ │ │ ▼ │ │ 初始化线程局部存储 │ │ skynet_initthread(THREAD_MONITOR) │ │ │ │ │ ▼ │ │ 主循环 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ while (!quit) { │ │ │ │ │ │ │ │ │ ├─ CHECK_ABORT │ │ │ │ │ (检查是否应该退出) │ │ │ │ │ │ │ │ │ ├─ 遍历所有 Worker 的监控器 │ │ │ │ │ for (i=0; i<worker_count; i++) { │ │ │ │ │ │ │ │ │ │ │ └─ skynet_monitor_check(m->m[i]) │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ 读取监控器状态 │ │ │ │ │ - version[i] │ │ │ │ │ - source[i] │ │ │ │ │ - check_version[i] │ │ │ │ │ - check_source[i] │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ 比较版本号 │ │ │ │ │ if (version != check_version) │ │ │ │ │ 正常,更新检查版本号 │ │ │ │ │ else │ │ │ │ │ 死循环,记录日志 │ │ │ │ │ } │ │ │ │ │ │ │ │ │ ├─ 休眠 5 秒 │ │ │ │ │ for (i=0; i<5; i++) { │ │ │ │ │ CHECK_ABORT │ │ │ │ │ sleep(1); │ │ │ │ │ } │ │ │ │ │ │ │ │ │ └─ 继续循环 │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ▼ │ │ 线程退出 │ │ │ │ │ ▼ │ │ return NULL │ │ │ └─────────────────────────────────────────────────────────────┘监控器数据结构
skynet_monitor 结构
文件:skynet/skynet-src/skynet_monitor.c
structskynet_monitor{intversion;// Worker 版本号intcheck_version;// Monitor 检查的版本号uint32_tsource;// 当前处理的服务句柄uint32_tcheck_source;// 检查时的服务句柄intwork_version;// 工作版本号intwork_source;// 工作源};字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| version | int | Worker 每处理一条消息递增,初始为 0 |
| check_version | int | Monitor 上次检查的版本号 |
| source | uint32_t | Worker 当前处理的服务句柄 |
| check_source | uint32_t | Monitor 上次检查的服务句柄 |
| work_version | int | 工作版本号(用于调试) |
| work_source | int | 工作源(用于调试) |
与 Worker 线程的交互
Worker 线程更新监控器
文件:skynet/skynet-src/skynet_monitor.c
voidskynet_monitor_trigger(structskynet_monitor*sm,intsource){sm->version=sm->version+1;// 版本号递增sm->source=source;// 设置当前服务句柄}Worker 线程清空监控器
文件:skynet/skynet-src/skynet_monitor.c
voidskynet_monitor_delete(structskynet_monitor*sm){sm->source=0;// 清空服务句柄}交互时序图
Worker 线程 Monitor 线程 │ │ │ 1. 取出消息 │ │ │ │ 2. skynet_monitor_trigger() │ │ ├─ version++ │ │ └─ source = handle │ │ │ │ 3. 处理消息 │ │ (可能需要 0.1-5 秒) │ │ │ │ 4. skynet_monitor_delete() │ │ └─ source = 0 │ │ │ │─────────────────────────────▶│ │ │ 5. skynet_monitor_check() │ │ ├─ version == check_version? │ │ ├─ 是 → 正常 │ │ └─ 否 → 死循环 │ │ │ 6. 取出下一条消息 │ │ │ │ 7. skynet_monitor_trigger() │ │ ├─ version++ │ │ └─ source = handle │ │ │ │─────────────────────────────▶ │ │ │ 8. skynet_monitor_check() │ │ ├─ 更新 check_version │ │ └─ 正常检测超时
除了死循环检测,Monitor 还可以检测服务处理超时。
超时检测逻辑
if(handle&&check_source==handle){// 同一个服务 5 秒内版本号未变化// 判定为死循环fprintf(stderr,"Deadloop detected in service %d\n",handle);// 检查是否超时uint32_ttime_cost=current_time-start_time;if(time_cost>5000){// 超过 5 秒fprintf(stderr,"Service %d timeout (%dms)\n",handle,time_cost);}}配置和调优
默认配置
| 配置项 | 默认值 | 说明 |
|---|---|---|
| 检查间隔 | 5 秒 | Monitor 每隔 5 秒检查一次 |
| 超时阈值 | 5 秒 | 超过 5 秒未完成视为死循环 |
调整检查间隔
如果需要更频繁的检查,可以修改代码:
// 原代码:休眠 5 秒for(i=0;i<5;i++){CHECK_ABORTsleep(1);}// 修改为:休眠 2 秒for(i=0;i<2;i++){CHECK_ABORTsleep(1);}注意: 更频繁的检查会增加 CPU 开销。
日志示例
正常情况
[时间戳] Service 0x00010002: Processing message... [时间戳] Service 0x00010002: Message processed (2ms)死循环检测
[时间戳] Deadloop detected in service 0x00010002 [时间戳] Service 0x00010002: Last action: skynet.call [时间戳] Service 0x00010002: Current function: handle_message总结
Monitor 线程的核心作用:
- 监控: 监控所有 Worker 线程的运行状态
- 检测: 检测服务死循环和超时
- 记录: 记录异常服务的详细信息
- 帮助: 帮助开发者定位问题
关键机制:
- 版本号机制:Worker 更新版本号,Monitor 检查版本号
- 定时检查:每隔 5 秒检查一次
- 非侵入式:不影响 Worker 的正常工作
与其他线程的关系:
- Worker 线程:被监控对象
- 主线程:创建 Monitor 线程
- 独立运行:不参与消息处理
