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) 启动Skynetskynet_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) {// 如果没有消息可处理 => sleeppthread_cond_wait(&m->cond, &m->mutex);}}return NULL;
}
- Worker 线程主要做消息分发处理(从全局队列/本地队列取出消息 => 交给相应的服务context去执行。)
- 如果没消息 =>
pthread_cond_wait
=> 休眠 => 等 Timer 或 Socket 线程唤醒。 weight
让某些线程能多处理几条消息(可实现不均衡分配, 保证核心Worker多干活)。
4. 结构化启动流程图
下图(示意)可帮助理解:
(1) main()|vskynet_globalinit() + skynet_env_init()|+--> parse config via Lua|vskynet_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|vskynet_globalexit()|vreturn
- 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 源码的起点,建立对整个启动过程和多线程分工有一个全局认识。
相关文章:
skynet 源码阅读 -- 启动主流程
Skynet 启动主流程分析 Skynet 是一个轻量级、高并发的服务器框架。它在启动时会进行一系列初始化操作,并启动多个不同功能的线程(Monitor、Timer、Worker、Socket),从而实现消息分发、定时器、网络I/O等核心功能。本文主要从 ma…...

OpenCV:高通滤波之索贝尔、沙尔和拉普拉斯
目录 简述 什么是高通滤波? 高通滤波的概念 应用场景 索贝尔算子 算子公式 实现代码 特点 沙尔算子 算子公式 实现代码 特点 拉普拉斯算子 算子公式 实现代码 特点 高通滤波器的对比与应用场景 相关阅读 OpenCV:图像滤波、卷积与卷积核…...

UDP 广播组播点播的区别及联系
1、网络IP地址的分类 组播地址是分类编址的IPv4地址中的D类地址,又叫多播地址,他的前四位必须是1110,所以网络地址的二进制取值范围是11100000~11101111对应的十进制为 224~~239。所以以224~239开头的网络地址都是组播地址。 组播地址的功能…...

STM32补充——IAP
0 前置知识: FLASH相关内容:前往STM32补充——FLASH STM32三种烧录方式(看看就行): 1.ISP:In System Programming(在系统编程) 执行芯片厂商的 Bootloader 程序进入 ISP 模式&…...

Jetson Xavier NX (ARM) 使用 PyTorch 安装 Open3D-ML 指南
由于 Jetson 为 ARM64 (aarch64) 的系统架构,所以不能用 pip install 直接安装,需要通过源码编译。 升级系统 JetPack 由于 Open3D-ML 目前只支持 CUDA 10.0 以及 CUDA 11.*,并且 JetPack 的 CUDA 开发环境只有10.2、11.4以及12.2࿰…...

【C++高并发服务器WebServer】-1:Linux中父子进程fork创建及关系、GDB多进程调试
本文目录 一、进程创建二、GDB多进程调试 一、进程创建 在Linux中输入man 2 fork可以查看man文档中的fork的相关函数信息。 fork的作用就是创建一个子进程。 通过fork我们可以知道,创建子进程的时候,复制父进程的信息。 我们看看翻译的man文档信息&am…...

C语言数组详解:从基础到进阶的全面解析
在C语言中,数组是一种基本的数据结构,用于存储多个相同类型的数据。数组的引入使得C语言能够高效地存储和操作大量数据。在任何一个C语言程序中,数组都发挥着极其重要的作用。无论是在算法实现、数据存储、还是在复杂程序的设计中,…...

docker的前世今生
docker来自哪里? 从我们运维部署的历史来看,宿主机从最初的物理机到虚拟机,再到docker,一步步演进到现在。技术演进其实是为了解决当前技术的痛点,那我们来看看有哪些痛点以及如何克服痛点的。 物理机 一般来说&…...
python实现施瓦茨-克里斯托费尔【全网首个】根据用户输入推测函数
上代码: from sympy import symbols, integrate, simplify from sympy.plotting import plotn int(input("n:")) if n < 2:print("Error: Must n > 2") i 0 a [] aef [] A [] x, y symbols(x y) z, w symbols(z w)while i < n…...

c语言中的数组(上)
数组的概念 数组是⼀组相同类型元素的集合; 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。 数组中存放的多个数据,类型是相同的。 数组分为⼀维数组和多维数组,多维数组⼀般⽐较多⻅的是⼆维数组。 数组创建 在C语言…...

Unity3D仿星露谷物语开发25之创建时钟界面
1、目标 在时钟界面显示当前时钟信息,同时设置特殊按钮可以快速推进时间用于测试。 2、创建GameClock.cs脚本 在Assets -> Scripts -> TimeSystem目录下创建GameClock.cs脚本。 代码如下: using System.Collections; using System.Collections…...

数据结构测试题1
一、选择题: 1.若长度为n的钱性表采用顺序存储结构,删除它的第i数据元素之前,需要先依次向前移动( )个数据元素。( C ) A .n-i B.ni C.n-i-1 D.n-i1 2.在单链表中,已知q指的结点是p指的结点的直接前驱结点&am…...
android wifi AsyncChannel(WifiManager和WifiP2pManager)
AynscChannel的讲解 [Android]AsyncChannel介绍-CSDN博客 WifiP2pManager里的channel的使用理解 WifiP2pManager.java public void createGroup(Channel c, ActionListener listener) {checkChannel(c);c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.NETWORK_ID_PE…...

【Image Captioning】DynRefer
DynRefer是由中国科学院大学于2024年提出的用于1种用于区域级多模态任务的模型。DynRefer 通过模拟人类视觉认知过程,显著提升了区域级多模态识别能力。通过引入人眼的动态分辨率机制, 能够以同时完成区域识别、区域属性检测和区域字幕生成任务。 文章链…...

Midjourney基础-常用修饰词+权重的用法大全
用好修饰词很关键 Midjourney要用除了掌握好提示词的写法,按照上一篇《做Midjourney最好图文教程-提示词公式以及高级参数讲解》画面主体 场景氛围 主体行为 构图方式 艺术风格 图像质量。 要画出有质感的内容我们必须要掌握好“修饰词”,这些修饰…...
没有屋檐的房子-023粪堆旁边的舞蹈
爱美是天性,贫苦的农村人也一样,贫苦的时代也一样。 本世纪,广场舞在华夏大地遍地开花,甚至都传到了外面。但是广场舞这种舞蹈形式并不是互联网时代的特产,也不是电声设备日益高级和普及时代的特产,更不是大…...

基于Docker的Kafka分布式集群
目录 1. 说明 2. 服务器规划 3. docker-compose文件 kafka{i}.yaml kafka-ui.yaml 4. kafka-ui配置集群监控 5. 参数表 6. 测试脚本 生产者-异步生产: AsyncKafkaProducer1.py 消费者-异步消费: AsyncKafkaConsumer1.py 7. 参考 1. 说明 创建一个本地开发环境所需的k…...

【博客之星】年度总结:在云影与墨香中探寻成长的足迹
🐇明明跟你说过:个人主页 🔖行路有良友,便是天堂🔖 目录 一、年度回顾 1、创作历程 2、个人成长 3、个人生活与博客事业 二、技术总结 1、赛道选择 2、技术工具 3、实战项目 三、前景与展望 1、云原生未来…...

SpringBoot的Swagger配置
一、Swagger配置 1.添加依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version> </dependency> 2.修改WebMvcConfig Slf4j Configurat…...

machine learning knn算法之使用KNN对鸢尾花数据集进行分类
通过导入必要的scikit-learn导入必要的库,加载给定的数据,划分测试集和训练集之后训练预测和评估即可 具体代码如下: import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split f…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...

rm视觉学习1-自瞄部分
首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...