skynet Worker 线程详解

Worker 线程详解

目录

  1. 概述
  2. 主要职责
  3. 权重机制
  4. 代码实现
  5. 消息调度流程
  6. 工作流程
  7. 负载均衡
  8. [与 Monitor 的交互](#与 Monitor 的交互)

概述

Worker 线程 是 Skynet 的消息处理线程,负责从全局消息队列中取出服务队列,处理服务消息。Worker 线程是 Skynet 的核心工作线程,所有的服务消息处理都在 Worker 线程中完成。

特点:

  • 多个 Worker 线程并行工作(默认 8 个)
  • 从全局队列中轮询获取服务队列
  • 采用权重机制实现负载均衡
  • 支持消息优先级和批处理

文件位置 : skynet/skynet-src/skynet_start.c

线程数量: 可配置,默认 8 个


主要职责

1. 从全局队列取出服务队列

功能: 从全局消息队列中取出需要处理的服务队列

取出策略:

  • 轮询全局队列
  • 避免重复取出同一队列
  • 优先处理有消息的队列

2. 处理服务消息

功能: 调用服务的回调函数处理消息

处理流程:

  1. 从服务队列中取出消息
  2. 更新监控器(防止死循环)
  3. 调用服务回调函数
  4. 清空监控器
  5. 处理下一条消息

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 线程

策略:

  1. 轮询全局队列: 每个 Worker 从全局队列取出服务队列
  2. 权重机制: 不同 Worker 处理不同数量的消息
  3. 优先级: 高权重 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 线程的核心作用:

  1. 消息处理: 从全局队列取出服务队列,处理服务消息
  2. 负载均衡: 根据权重机制实现负载均衡
  3. 并发处理: 多个 Worker 并行处理不同服务的消息
  4. 监控协作: 与 Monitor 线程协作检测死循环

关键机制:

  • 权重机制:控制处理策略
  • 轮询机制:均匀分配负载
  • 休眠唤醒:无消息时休眠,有消息时唤醒

与其他线程的关系:

  • Monitor 线程:监控 Worker 是否死循环
  • Timer 线程:唤醒 Worker 处理定时器消息
  • Socket 线程:唤醒 Worker 处理网络消息
  • 主线程:创建 Worker 线程
相关推荐
lpl3129055095 小时前
skynet 主线程 (Main Thread) 详解
lua
lpl3129055096 小时前
skynet Socket 线程详解
lua
林鸿群1 天前
Cocos2d-x Lua 游戏前端工程架构深度解析
游戏·mvc·lua·游戏开发·cocos2d·游戏架构
林鸿群1 天前
Lua 5.4 语法与核心知识学习总结
lua
007张三丰2 天前
软件测试专栏(7/20):接口测试全攻略:Postman+Newman实现API自动化
自动化·lua·接口测试·postman·api测试·newman
于眠牧北2 天前
重写RedisTemplate后在lua脚本中传递参数不需要二次转换
java·junit·lua
csdn_aspnet2 天前
技术难题:高并发场景下的“超卖”现象(库存一致性)
redis·lua·秒杀
shuair2 天前
redis执行lua脚本
数据库·redis·lua
小白-Tester3 天前
2026最新Postman安装教程[简单易懂]附安装包
开发语言·lua