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;
}
流程要点:
- 读取命令行参数:确定配置文件
config_file
。 - Skynet全局初始化:
skynet_globalinit
/skynet_env_init
设置一些全局环境,注册信号处理等。 - 解析配置:通过 Lua 脚本来读取
config_file
并存到struct skynet_config
。 - 调用
skynet_start
:是 Skynet 的核心启动函数,后面会详细讲。 - 清理:退出时
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 模块初始化
skynet_harbor_init
:与分布式/harbor机制有关(分布式集群的一部分)。skynet_handle_init
:管理 “Handle -> Service” 映射。skynet_mq_init
:初始化全局消息队列结构(global_queue
)。skynet_module_init
:C服务的模块管理,如加载cpath
下的.so
。skynet_timer_init
:定时器初始化,创建TI = timer_create_timer()
并记录当前系统时间。skynet_socket_init
:socket层初始化,创建socket_server
。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_exit
、skynet_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. 小结
- 主进程先解析配置,初始化一些全局,随后调用
skynet_start
。 - skynet_start 依次初始化 harbor、handle、mq、module、timer、socket…
- 启动logservice(记录日志)和bootstrap(初始Lua服务),最后启动多线程(monitor/timer/socket/worker)。
- 整个 Skynet 运行后,Timer 线程负责定时器 & 唤醒 Worker,Socket 线程负责网络事件,Worker 处理消息循环,Monitor检查 Worker 是否卡死。
通过这样一个启动流程,Skynet 建立起一个消息驱动的并发系统框架:
- Worker 负责业务逻辑
- Socket 处理IO
- Timer 提供定时&超时功能
- Monitor 监控
后续若要深入,可阅读每个线程对应函数(如 thread_timer
, thread_worker
)里更细节的流程,比如如何 dispatch 消息,如何在定时器中移位管理远期任务等。本篇作为阅读 Skynet 源码的起点,建立对整个启动过程和多线程分工有一个全局认识。