Worker 线程详解
目录
概述
Worker 线程 是 Skynet 的消息处理线程,负责从全局消息队列中取出服务队列,处理服务消息。Worker 线程是 Skynet 的核心工作线程,所有的服务消息处理都在 Worker 线程中完成。
特点:
- 多个 Worker 线程并行工作(默认 8 个)
- 从全局队列中轮询获取服务队列
- 采用权重机制实现负载均衡
- 支持消息优先级和批处理
文件位置 : skynet/skynet-src/skynet_start.c
线程数量: 可配置,默认 8 个
主要职责
1. 从全局队列取出服务队列
功能: 从全局消息队列中取出需要处理的服务队列
取出策略:
- 轮询全局队列
- 避免重复取出同一队列
- 优先处理有消息的队列
2. 处理服务消息
功能: 调用服务的回调函数处理消息
处理流程:
- 从服务队列中取出消息
- 更新监控器(防止死循环)
- 调用服务回调函数
- 清空监控器
- 处理下一条消息
3. 负载均衡
功能: 根据权重决定处理策略,实现负载均衡
权重类型:
-1: 只处理本 worker 负责的服务0: 处理本 worker 的服务,也帮忙处理其他服务1, 2, 3: 越来越愿意帮忙处理其他服务
4. 无消息时休眠
功能: 当没有消息可处理时,进入休眠状态
休眠机制:
- 使用条件变量
pthread_cond_wait() - 被 Timer 或 Socket 线程唤醒
- 虚假唤醒无害
权重机制
权重配置
文件 : skynet/skynet-src/skynet_start.c
位置 : skynet_start.c:424-430
c
static int weight[] = {
-1, -1, -1, -1, // Worker 0-3: 只处理自己的服务
0, 0, 0, 0, // Worker 4-7: 处理自己的,也帮忙处理其他
1, 1, 1, 1, // Worker 8-11: 更愿意帮忙处理其他
1, 1, 1, 1,
2, 2, 2, 2, // Worker 16-23: 非常愿意帮忙处理其他
2, 2, 2, 2,
3, 3, 3, 3, // Worker 24-31: 总是优先处理其他服务
3, 3, 3, 3,
};
权重含义
| 权重值 | 含义 | 处理策略 |
|---|---|---|
| -1 | 只处理自己的服务 | dispatch(q, -1) - 只处理一条消息,然后返回队列 |
| 0 | 处理自己的,也帮忙处理其他 | dispatch(q, 0) - 处理队列中所有消息 |
| 1 | 更愿意帮忙处理其他 | dispatch(q, 1) - 处理 len >> 1 条消息 |
| 2 | 非常愿意帮忙处理其他 | dispatch(q, 2) - 处理 len >> 2 条消息 |
| 3 | 总是优先处理其他 | dispatch(q, 3) - 处理 len >> 3 条消息 |
权重示例
假设队列中有 8 条消息:
Worker 0 (weight = -1):
- 只处理 1 条消息
- 处理完就返回,让其他 worker 处理
Worker 4 (weight = 0):
- 处理 8 条消息(全部)
- 处理完所有消息才返回
Worker 8 (weight = 1):
- 处理 8 >> 1 = 4 条消息
- 处理一半消息后返回
Worker 16 (weight = 2):
- 处理 8 >> 2 = 2 条消息
- 处理四分之一消息后返回
Worker 24 (weight = 3):
- 处理 8 >> 3 = 1 条消息
- 处理八分之一消息后返回
代码实现
Worker 线程主函数
文件 : skynet/skynet-src/skynet_start.c
位置 : skynet_start.c:325-364
c
static void *
thread_worker(void *p) {
struct worker_parm *wp = p;
int id = wp->id; // Worker ID
int weight = wp->weight; // 权重
struct monitor *m = wp->m; // 监控器
struct skynet_monitor *sm = m->m[id]; // 本 worker 的监控器
skynet_initthread(THREAD_WORKER); // 初始化线程局部存储
struct message_queue * q = NULL; // 当前正在处理的服务队列
while (!m->quit) {
// 尝试分发消息
// q: 当前队列,如果不为 NULL,优先处理该队列
// weight: 权重,影响负载均衡策略
q = skynet_context_message_dispatch(sm, q, weight);
if (q == NULL) {
// 没有消息可处理,进入休眠
if (pthread_mutex_lock(&m->mutex) == 0) {
++ m->sleep; // 增加休眠计数
// "spurious wakeup" is harmless,
// because skynet_context_message_dispatch() can be call at any time.
// "虚假唤醒"是无害的,因为 dispatch 可以在任何时候调用
if (!m->quit)
pthread_cond_wait(&m->cond, &m->mutex); // 等待条件变量
-- m->sleep; // 减少休眠计数
if (pthread_mutex_unlock(&m->mutex)) {
fprintf(stderr, "unlock mutex error");
exit(1);
}
}
}
}
return NULL;
}
消息分发函数
文件 : skynet/skynet-src/skynet_server.c
c
struct message_queue *
skynet_context_message_dispatch(struct skynet_monitor *sm,
struct message_queue *q,
int weight) {
if (q == NULL) {
// 从全局队列取出一个服务队列
q = skynet_globalmq_pop();
if (q == NULL) {
return NULL; // 没有队列可处理
}
}
// 获取服务上下文
uint32_t handle = skynet_mq_handle(q);
struct skynet_context * ctx = skynet_handle_grab(handle);
if (ctx == NULL) {
// 服务已退出,释放队列
skynet_mq_release(q, drop_message, NULL);
return NULL;
}
// 计算要处理的消息数量
int n = 1;
if (weight >= 0) {
// weight >= 0,计算处理数量
n = skynet_mq_length(q);
n >>= weight; // 右移 weight 位
} else {
// weight = -1,只处理一条消息
n = 1;
}
// 处理 n 条消息
int i;
for (i = 0; i < n; i++) {
struct skynet_message msg;
if (skynet_mq_pop(q, &msg)) {
// 队列为空,停止处理
break;
}
// 更新监控器
skynet_monitor_trigger(sm, msg.source);
// 调用服务回调函数
int overload = 0;
if (ctx->cb == NULL) {
// 服务没有回调函数,释放消息
skynet_error(ctx, "Drop message from %x (source = %08x)",
handle, msg.source);
skynet_free(msg.data);
} else {
// 调用回调函数
overload = ctx->cb(ctx, ctx->cb_ud,
msg.type, msg.session,
msg.source, msg.data, msg.sz);
}
// 清空监控器
skynet_monitor_delete(sm);
// 检查过载
if (overload) {
// 服务过载,停止处理
break;
}
}
skynet_handle_release(ctx);
// 返回队列,继续处理或放回全局队列
if (skynet_mq_length(q) == 0) {
// 队列为空,不需要再处理
return NULL;
} else {
// 队列还有消息,继续处理
return q;
}
}
消息调度流程
┌─────────────────────────────────────────────────────────────┐
│ 消息调度流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Worker 线程 │
│ │ │
│ ▼ │
│ skynet_context_message_dispatch(sm, q, weight) │
│ │ │
│ ├─ if (q == NULL) │
│ │ └─ 从全局队列取出一个队列 │
│ │ q = skynet_globalmq_pop() │
│ │ │
│ ├─ 获取服务上下文 │
│ │ ctx = skynet_handle_grab(handle) │
│ │ │
│ ├─ 计算要处理的消息数量 │
│ │ if (weight >= 0) │
│ │ n = len >> weight │
│ │ else │
│ │ n = 1 │
│ │ │
│ ├─ 处理 n 条消息 │
│ │ for (i = 0; i < n; i++) { │
│ │ │ │
│ │ ├─ skynet_mq_pop(q, &msg) │
│ │ │ 从队列中取出消息 │
│ │ │ │
│ │ ├─ skynet_monitor_trigger(sm, msg.source) │
│ │ │ 更新监控器 │
│ │ │ │
│ │ ├─ ctx->cb(ctx, ...) │
│ │ │ 调用服务回调函数 │
│ │ │ │
│ │ ├─ skynet_monitor_delete(sm) │
│ │ │ 清空监控器 │
│ │ │ │
│ │ └─ 继续下一条消息 │
│ │ } │
│ │ │
│ ├─ skynet_handle_release(ctx) │
│ │ 释放服务引用 │
│ │ │
│ └─ 返回队列 │
│ if (队列为空) │
│ return NULL │
│ else │
│ return q // 继续处理 │
│ │
└─────────────────────────────────────────────────────────────┘
工作流程
┌─────────────────────────────────────────────────────────────┐
│ Worker 线程工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 线程启动 │
│ │ │
│ ▼ │
│ 初始化线程局部存储 │
│ skynet_initthread(THREAD_WORKER) │
│ │ │
│ ▼ │
│ 主循环 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ while (!quit) { │ │
│ │ │ │ │
│ │ ├─ skynet_context_message_dispatch(sm, q, │ │
│ │ │ weight) │ │
│ │ │ │ │ │
│ │ │ ├─ 从全局队列取出队列 │ │
│ │ │ │ q = skynet_globalmq_pop() │ │
│ │ │ │ │ │
│ │ │ ├─ 获取服务上下文 │ │
│ │ │ │ ctx = skynet_handle_grab(handle) │ │
│ │ │ │ │ │
│ │ │ ├─ 计算处理数量 │ │
│ │ │ │ if (weight >= 0) │ │
│ │ │ │ n = len >> weight │ │
│ │ │ │ else │ │
│ │ │ │ n = 1 │ │
│ │ │ │ │ │
│ │ │ ├─ 处理消息 │ │
│ │ │ │ for (i=0; i<n; i++) { │ │
│ │ │ │ skynet_mq_pop(q, &msg) │ │
│ │ │ │ skynet_monitor_trigger(sm, ...) │ │
│ │ │ │ ctx->cb(ctx, ...) │ │
│ │ │ │ skynet_monitor_delete(sm) │ │
│ │ │ │ } │ │
│ │ │ │ │ │
│ │ │ └─ 释放服务引用 │ │
│ │ │ skynet_handle_release(ctx) │ │
│ │ │ │ │
│ │ └─ if (q == NULL) │ │
│ │ │ │ │
│ │ ├─ 进入休眠 │ │
│ │ │ pthread_mutex_lock(&m->mutex) │ │
│ │ │ ++ m->sleep │ │
│ │ │ pthread_cond_wait(&m->cond, ...) │ │
│ │ │ -- m->sleep │ │
│ │ │ pthread_mutex_unlock(&m->mutex) │ │
│ │ │ │ │
│ │ └─ 继续循环 │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ▼ │
│ return NULL │
│ │
└─────────────────────────────────────────────────────────────┘
负载均衡
负载均衡策略
目标: 将消息均匀分配到各个 Worker 线程
策略:
- 轮询全局队列: 每个 Worker 从全局队列取出服务队列
- 权重机制: 不同 Worker 处理不同数量的消息
- 优先级: 高权重 Worker 更愿意处理其他服务
负载均衡示例
场景: 8 个 Worker,10 个服务,每个服务有 100 条消息
Worker 0 (weight = -1):
- 只处理自己的服务
- 处理 100 条消息后返回
Worker 4 (weight = 0):
- 处理自己的服务,也帮忙处理其他
- 可能处理 200 条消息
Worker 8 (weight = 1):
- 更愿意帮忙处理其他
- 可能处理 150 条消息
Worker 16 (weight = 2):
- 非常愿意帮忙处理其他
- 可能处理 125 条消息
Worker 24 (weight = 3):
- 总是优先处理其他
- 可能处理 112 条消息
结果: 消息均匀分配到各个 Worker
调整负载均衡
如果需要更均匀的负载均衡,可以调整权重:
c
// 所有 Worker 的权重都设置为 0
for (i=0; i<thread; i++) {
wp[i].weight = 0;
}
效果: 所有 Worker 都会帮忙处理其他服务,负载更均衡。
与 Monitor 的交互
监控器更新
消息处理前:
c
// 更新监控器,开始处理消息
skynet_monitor_trigger(sm, msg.source);
// 版本号递增
sm->version++;
sm->source = msg.source;
消息处理后:
c
// 清空监控器,消息处理完成
skynet_monitor_delete(sm);
// 清空服务句柄
sm->source = 0;
死循环检测
Worker 线程 Monitor 线程
│ │
│ 1. 取出消息 │
│ skynet_monitor_trigger() │
│ version++ │
│ │
│ 2. 处理消息 │
│ (可能需要 5 秒) │
│ │
│ 3. skynet_monitor_delete() │
│ source = 0 │
│ │
│─────────────────────────────▶│
│ │ 4. skynet_monitor_check()
│ │ 检测到死循环
│ │ version 未变化
│ │ 记录日志
配置和调优
默认配置
| 配置项 | 默认值 | 说明 |
|---|---|---|
| thread | 8 | Worker 线程数量 |
| weight | 数组 | Worker 权重配置 |
调整 Worker 数量
lua
-- skynet.conf
thread = 16 -- 增加到 16 个 Worker
调整权重
如果需要自定义权重,可以修改代码:
c
// 自定义权重配置
static int weight[] = {
0, 0, 0, 0, // Worker 0-3
0, 0, 0, 0, // Worker 4-7
0, 0, 0, 0, // Worker 8-11
// ... 更多 Worker
};
使用示例
服务回调函数
lua
local skynet = require "skynet"
skynet.start(function()
skynet.dispatch("lua", function(session, source, cmd, ...)
local f = CMD[cmd]
if f then
skynet.ret(skynet.pack(f(...)))
else
skynet.error("Unknown command:", cmd)
end
end)
end)
C 服务回调函数
c
static int
callback(struct skynet_context * context, void *ud,
int type, int session, uint32_t source,
const void * msg, size_t sz) {
switch (type) {
case PTYPE_TEXT:
printf("收到消息: %s\n", (char *)msg);
break;
case PTYPE_LUA:
// 处理 Lua 消息
break;
default:
break;
}
return 0;
}
常见问题
Q1: Worker 线程会处理所有服务的消息吗?
答案: 不会,每个 Worker 只处理全局队列中的服务队列,不会跨线程处理同一服务的消息。
Q2: Worker 线程如何休眠和唤醒?
答案 : 使用条件变量 pthread_cond_wait() 休眠,被 Timer 或 Socket 线程唤醒。
Q3: 如何增加 Worker 线程数量?
答案 : 在配置文件中设置 thread = N。
Q4: Worker 线程会死锁吗?
答案: 不会,因为每个 Worker 只处理一条消息队列,不会相互等待。
总结
Worker 线程的核心作用:
- 消息处理: 从全局队列取出服务队列,处理服务消息
- 负载均衡: 根据权重机制实现负载均衡
- 并发处理: 多个 Worker 并行处理不同服务的消息
- 监控协作: 与 Monitor 线程协作检测死循环
关键机制:
- 权重机制:控制处理策略
- 轮询机制:均匀分配负载
- 休眠唤醒:无消息时休眠,有消息时唤醒
与其他线程的关系:
- Monitor 线程:监控 Worker 是否死循环
- Timer 线程:唤醒 Worker 处理定时器消息
- Socket 线程:唤醒 Worker 处理网络消息
- 主线程:创建 Worker 线程