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 线程
相关推荐
列星随旋13 小时前
基于 Redis + Lua,实现“多维度原子限流”(令牌桶 + 滑动窗口)
java·redis·lua
上海合宙LuatOS13 小时前
LuatOS扩展库API——【exgnss】GNSS定位
物联网·lua·luatos
0xDevNull13 小时前
Redis Lua 脚本详细教程
redis·缓存·lua
上海合宙LuatOS14 小时前
LuatOS扩展库API——【exlcd】显示屏控制
物联网·lua·luatos
0xDevNull14 小时前
Spring Boot 中使用 Redis Lua 脚本详细教程
spring boot·redis·lua
DJ斯特拉2 天前
Redis使用lua脚本
junit·单元测试·lua
Aktx20FNz2 天前
OpenClaw中级到高级教程
lua
LcGero3 天前
Lua + Cocos Creator 实战:用 Lua 驱动 UI 与游戏逻辑
游戏·ui·lua
静心观复5 天前
Lua 脚本是什么
开发语言·lua
LcGero5 天前
Lua 协程(Coroutine):游戏里的“伪多线程”利器
游戏·lua·游戏开发·协程