skynet_start.c
skynet-src/skynet_start.c 实现了 skynet 的多线程工作机制和任务调度
是 skynet 多线程 Actor 模型的运行载体
skynet 的线程
skynet 主要有以下线程:
序号 | 线程 | 说明 |
---|---|---|
0 号线程 | thread_monitor | 检测服务(Actor)是否死锁 |
1 号线程 | thread_timer | 2500微妙唤醒一次,执行定时器(push 定时器消息给 Actor) |
2 号线程 | thread_socket | 处理 epoll 网络数据接收发 |
3 号线程开始(配置根数量) | thread_worker | 工作线程。并发处理 Actor 消息 |
skynet 服务(Actor)
工作线程执行的都是 Actor 消息处理
在 skynet 中以下都是 Actor 同义词
命名 | 语言 | 说明 |
---|---|---|
module | c++ | skynet c++ 层, module 就是一个 Actor |
service | lua | skynet lua 层, 服务就是一个 Actor |
skynet_context | c++ | module service 都是用 skynet_context 实例。所说的 Actor 实际上指的就是这货 |
actor | 口语 | skynet 是 Actor 模型的一个实现,因此交流中都会用 Actor 来表示代码中的 service 或 module |
module 和 service 还有一点区别:
- service 由 skynet 的 module snlua 创建
- 每个 service 有自己的 lua 虚拟机,因此运行环境完全独立
Actor 模型中, Actor 间通过消息交互。因此 skynet 的 module service 均有自己的消息队列
全局消息队列
为了能让 Actor 高性能并发执行, skynet 中设置了全局消息队列
- 全局消息队列中,存放的是 Actor 的消息队列
- Actor 的消息队列,存放的是消息
工作线程,要执行 Actor 消息,首先从全局消息队列摘取 Actor 的消息队列
- 这样,不会有同时 2 根线程执行同个 Actor 消息
- 因此 Actor 消息执行过程也就不需要加锁
- 不同 Actor 消息可以并发执行
工作线程工作流程
伪代码:
while
Actor 消息队列 = pop 全局消息队列
如果有
执行 Actor 消息( n 条)
push 消息队列回全局消息队列
否则
sleep
skynet 工作线程优先级
为了让每个 Actor 公平分配到 cpu ,防止饿死情况
skynet 给工作线程设置了优先级
启动工作线程时,会赋值 weight 确定其优先级
weight | 哪些线程 | 执行的消息数量 | 说明 |
---|---|---|---|
-1 | [3, 6]号线程 | 1 条 Actor 消息。 | 这 4 根是保底每个 Actor 不会被饿死 |
0 | [7, 10]号线程以及 [35, 无穷大)号线程之后的线程 | 本次 Actor 消息队列中的所有消息 | 最高性能的方式 |
1 | [11, 18]号线程 | 1/2 条 | |
2 | [19, 26]号线程 | 1/4 条 | |
3 | [27, 24]号线程 | 1/8 条 |
工作线程唤醒机制
工作线程在没消息后,会 sleep
通过以下重新唤醒:
- thread_timer ,每 2500 微妙,检查如果有 sleep 线程,会随机唤醒 1 根
- thread_socket ,有网络消息了,检查如果所有的线程均在 sleep ,会随机唤醒 1 根
工作线程死锁检查机制
每根线程维护着,消息处理次数(版本号)
thread_monitor 每 5 秒检查 1 次,如果版本号不变,打印日志警告,有线程可能死锁了