当前位置: 首页 > article >正文

skynet 源码阅读 -- 启动主流程

Skynet 启动主流程分析

Skynet 是一个轻量级、高并发的服务器框架。它在启动时会进行一系列初始化操作,并启动多个不同功能的线程(Monitor、Timer、Worker、Socket),从而实现消息分发、定时器、网络I/O等核心功能。本文主要从 main() 函数开始一步步trace,循序渐进地看 Skynet 的启动过程以及各条线程的分工,为后续深入阅读 Skynet 源码做铺垫。


1. 启动入口 main 函数 -- skynet_main.c

int
main(int argc, char *argv[]) {
    // 1) 获取配置文件
    const char * config_file = NULL ;
    if (argc > 1) {
        config_file = argv[1];
    } else {
        fprintf(stderr, "Need a config file. usage: skynet configfilename\n");
        return 1;
    }

    // 2) Skynet全局初始化
    skynet_globalinit();
    skynet_env_init();
    sigign();  // 忽略部分信号

    struct skynet_config config;

    // 3) 解析配置文件
    struct lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    // load_config 脚本读取 config_file 并将其返回到 Lua
    // ...
    _init_env(L);  // 将Lua中的配置项保存到C层
    lua_close(L);

    // 4) 将 Lua 解析到的配置写入 config 结构体
    config.thread =  optint("thread",8);
    config.module_path = optstring("cpath","./cservice/?.so");
    config.harbor = optint("harbor", 1);
    config.bootstrap = optstring("bootstrap","snlua bootstrap");
    config.daemon = optstring("daemon", NULL);
    config.logger = optstring("logger", NULL);
    config.logservice = optstring("logservice", "logger");
    config.profile = optboolean("profile", 1);

    // 5) 启动Skynet
    skynet_start(&config);

    // 6) 全局退出清理
    skynet_globalexit();

    return 0;
}

流程要点:

  1. 读取命令行参数:确定配置文件 config_file
  2. Skynet全局初始化skynet_globalinit / skynet_env_init 设置一些全局环境,注册信号处理等。
  3. 解析配置:通过 Lua 脚本来读取 config_file 并存到 struct skynet_config
  4. 调用 skynet_start:是 Skynet 的核心启动函数,后面会详细讲。
  5. 清理:退出时 skynet_globalexit 做一些释放资源操作(比如内存管理模块等)。

从这里可以看到:Lua 脚本用于配置 Skynet,通过 Lua 作为配置及引擎脚本。


2. skynet_start:初始化模块 & 启动多线程

void 
skynet_start(struct skynet_config * config) {
    // 1) 注册SIGHUP用来重开日志文件
    struct sigaction sa;
    sa.sa_handler = &handle_hup;
    sa.sa_flags = SA_RESTART;
    sigfillset(&sa.sa_mask);
    sigaction(SIGHUP, &sa, NULL);

    // 2) 若配置了daemon模式 -> 后台运行
    if (config->daemon) {
        if (daemon_init(config->daemon)) {
            exit(1);
        }
    }

    // 3) 各模块初始化
    skynet_harbor_init(config->harbor);
    skynet_handle_init(config->harbor);
    skynet_mq_init();
    skynet_module_init(config->module_path);
    skynet_timer_init();
    skynet_socket_init();
    skynet_profile_enable(config->profile);

    // 4) 启动 logservice 服务
    struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
    if (ctx == NULL) {
        fprintf(stderr, "Can't launch %s service\n", config->logservice);
        exit(1);
    }
    skynet_handle_namehandle(skynet_context_handle(ctx), "logger");

    // 5) 启动 bootstrap 服务 
    bootstrap(ctx, config->bootstrap);

    // 6) 启动多线程
    start(config->thread);

    // 7) 退出处理
    skynet_harbor_exit();
    skynet_socket_free();
    if (config->daemon) {
        daemon_exit(config->daemon);
    }
}

2.1 模块初始化

  1. skynet_harbor_init:与分布式/harbor机制有关(分布式集群的一部分)。
  2. skynet_handle_init:管理 “Handle -> Service” 映射。
  3. skynet_mq_init:初始化全局消息队列结构(global_queue)。
  4. skynet_module_init:C服务的模块管理,如加载 cpath 下的 .so
  5. skynet_timer_init:定时器初始化,创建 TI = timer_create_timer() 并记录当前系统时间。
  6. skynet_socket_init:socket层初始化,创建 socket_server
  7. skynet_profile_enable:若 config->profile 为真,打开性能分析。

2.2 启动初始服务

  • logservice:日志服务,用于记录日志(logger服务)。
  • bootstrap: 会启动launcher skynet.launch("snlua","launcher") 服务,后续用作lua 服务的启动器。

2.3 启动线程

start(config->thread);
  • Skynet 同时需要多个线程来协同工作:Monitor 线程Timer 线程Socket 线程、以及 N 个 Worker 线程
  • 这样就形成了一个 Skynet 进程 有多条并行线程,分别干不同的事(定时器、网络I/O、处理消息等)。
  • 具体见 static void start(int thread)

2.4 退出

  • 当线程全部 join() 完后,会执行 skynet_harbor_exitskynet_socket_free 等操作收尾。

3. start函数:创建并管理多条线程

static void
start(int thread) {
    pthread_t pid[thread+3];

    // 1) 创建 monitor 结构
    struct monitor *m = skynet_malloc(sizeof(*m));
    memset(m, 0, sizeof(*m));
    m->count = thread;
    m->sleep = 0;
    m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *));
    // 初始化互斥量, 条件变量

    // 2) 创建3条特殊线程
    create_thread(&pid[0], thread_monitor, m);
    create_thread(&pid[1], thread_timer, m);
    create_thread(&pid[2], thread_socket, m);

    // 3) 创建 worker 线程
    struct worker_parm wp[thread];
    for (i=0;i<thread;i++) {
        wp[i].m = m;
        wp[i].id = i;
        ...
        create_thread(&pid[i+3], thread_worker, &wp[i]);
    }

    // 4) join 所有线程
    for (i=0;i<thread+3;i++) {
        pthread_join(pid[i], NULL);
    }

    free_monitor(m);
}

由此可见:

  • Monitor 线程thread_monitor
  • Timer 线程thread_timer
  • Socket 线程thread_socket
  • Worker 线程thread_worker (数量 = thread)

总线程数 = thread + 3

3.1 Monitor 线程

static void *
thread_monitor(void *p) {
    struct monitor * m = p;
    ...
    for (;;) {
        // 定期检查 worker 是否卡死
        for (i=0;i<n;i++) {
            skynet_monitor_check(m->m[i]);
        }
        sleep(1);
    }
    return NULL;
}
  • 用于监控 Worker 线程是否卡死,通过 skynet_monitor_check 观测 worker 执行时间。
  • 如果检测到超长执行,会打印报警或采取措施(避免Worker永久阻塞)。

3.2 Timer 线程

static void *
thread_timer(void *p) {
    struct monitor * m = p;
    for (;;) {
        skynet_updatetime();       // 更新定时器
        skynet_socket_updatetime();// socket层时间更新
        wakeup(m,m->count-1);     // 唤醒所有线程( 让 worker 从 cond.wait 中唤醒 )
        usleep(2500);             // 2.5 ms 间隔
        ...
    }
    ...
    return NULL;
}
  • skynet_updatetime:增加 “逻辑时间” 并执行到期任务
  • skynet_socket_updatetime:驱动 socket server 中一些超时逻辑
  • wakeup:唤醒 Worker,防止他们陷入空闲
  • usleep(2500) => 每 2.5ms tick 一次

3.3 Socket 线程

static void *
thread_socket(void *p) {
    for (;;) {
        int r = skynet_socket_poll();
        if (r==0)
            break;
        if (r<0) {
            continue;
        }
        wakeup(m,0);
    }
    return NULL;
}
  • 这里循环调用 skynet_socket_poll(),监听网络事件(读写就绪、连接、断开等)。
  • r==0 表示 socket server 退出 => break
  • 每次有网络事件 => wakeup => 唤醒 Worker 线程去处理消息

3.4 Worker 线程

static void *
thread_worker(void *p) {
    struct worker_parm *wp = p;
    while (!m->quit) {
        // 1) 分发一条消息
        q = skynet_context_message_dispatch(sm, q, weight);
        if (q == NULL) {
            // 如果没有消息可处理 => sleep
            pthread_cond_wait(&m->cond, &m->mutex);
        }
    }
    return NULL;
}
  • Worker 线程主要做消息分发处理(从全局队列/本地队列取出消息 => 交给相应的服务context去执行。)
  • 如果没消息 => pthread_cond_wait => 休眠 => 等 Timer 或 Socket 线程唤醒。
  • weight 让某些线程能多处理几条消息(可实现不均衡分配, 保证核心Worker多干活)。

4. 结构化启动流程图

下图(示意)可帮助理解:

(1) main()
     |
     v
   skynet_globalinit() + skynet_env_init()
     |
     +--> parse config via Lua
     |
     v
   skynet_start(&config)
     |--- register SIGHUP
     |--- if daemon => daemon_init
     |--- skynet_*_init:
     |      - harbor_init
     |      - handle_init
     |      - mq_init
     |      - module_init
     |      - timer_init
     |      - socket_init
     |
     |--- create logservice => "logger"
     |--- bootstrap => e.g. "snlua bootstrap"
     |
     |--- start(#thread)
          |
          +-> create monitor thread
          +-> create timer thread
          +-> create socket thread
          +-> create N worker threads
          |
          +-> all threads join => end
     |
     v
   skynet_globalexit()
     |
     v
   return
  • main:解析 config, 调用 skynet_start
  • skynet_start:初始化模块/服务 => 启动多线程 => run => join => exit
  • 各个线程并行运作:
    • Monitor => 检测卡死
    • Timer => 定时器滴答 & 唤醒 worker
    • Socket => 处理网络事件
    • Worker => 真正执行 Lua 服务消息

5. 小结

  1. 主进程先解析配置,初始化一些全局,随后调用 skynet_start
  2. skynet_start 依次初始化 harbor、handle、mq、module、timer、socket…
  3. 启动logservice(记录日志)和bootstrap(初始Lua服务),最后启动多线程(monitor/timer/socket/worker)。
  4. 整个 Skynet 运行后,Timer 线程负责定时器 & 唤醒 Worker,Socket 线程负责网络事件,Worker 处理消息循环,Monitor检查 Worker 是否卡死。

通过这样一个启动流程,Skynet 建立起一个消息驱动的并发系统框架:

  • Worker 负责业务逻辑
  • Socket 处理IO
  • Timer 提供定时&超时功能
  • Monitor 监控

后续若要深入,可阅读每个线程对应函数(如 thread_timer, thread_worker)里更细节的流程,比如如何 dispatch 消息,如何在定时器中移位管理远期任务等。本篇作为阅读 Skynet 源码的起点,建立对整个启动过程多线程分工有一个全局认识。


http://www.kler.cn/a/515332.html

相关文章:

  • 2025.1.20——一、[RCTF2015]EasySQL1 二次注入|报错注入|代码审计
  • 如何使用 Redis 作为高效缓存
  • 快手SDK接入错误处理经验总结(WebGL方案)
  • c++学习第七天
  • node.js 文件操作
  • 游戏AI,让AI 玩游戏有什么作用?
  • Vue2.0+ElementUI实现查询条件展开和收起功能组件
  • 速通Docker === 快速部署Redis主从集群
  • 如何统计字符串中单词出现的次数
  • 新阿里云买服务器配置需手动配置80端口
  • ChatGPT是强人工智能吗?
  • 【二叉树的深搜】计算布尔二叉树的值 求根节点到叶节点数字之和
  • 安全生产算法一体机定制
  • 软件工程的基本原理
  • 机器学习 vs 深度学习
  • 【运维】什么是Ubantu?安装网址
  • docker 简要笔记
  • 深入内核讲明白Android Binder【三】
  • MPP数据库:大数据处理的“高手”
  • linux-mysql在centos7安装和基础配置
  • 基于Andirod+SQLite实现的记账本APP
  • 微信小程序实现自定义日历功能
  • 循环队列(C语言)
  • 3.CSS的背景
  • 【json_object】mysql中json_object函数过长,显示不全
  • 安装线程自由(无GIL锁)Python及Pytorch方法