二级链表构造
简介
Skynet 为了消息处理实现了一个二级链表:
如下图所示 , 第一级链表是一个基于动态开辟节点实现的链表, 每一个节点对应一个服务单元,第二级链表是一个由数组实现的链表(需要时会以2倍的规模扩容),存储的是这个服务单元的所有待处理消息。
图示:
功能点
- 每个节点对应一个独立的服务单元。
- 加锁
- 一级链表自带自旋锁,但是仅仅在Push和Pop一个节点的时候加锁即可
- 二级链表同样自带自旋锁,在Push和Pop一个Message的时候加锁即可
- Message处理阶段无需加锁
- 服务单元处理消息的回调函数无需线程安全
- 每个节点被Worker线程通过Pop拿到节点后,在处理完它应该处理的Message之前不会将节点Push回一级链表, 故而单个服务单元的回调函数不会被多个Worker线程并发执行。
- Message为空的节点不会被添加回一级链表。
- Message由空转非空则添加回一级链表。
Worker线程
线程池果然是王道。
处理逻辑
- 每个空闲Worker线程从一级链表中取出一个节点(一定是Message非空),处理之。
- Worker线程检查节点对应的服务是否仍然存在,不存在则Release节点。存在则切换上下文,执行对应服务单元的包处理函数。
- Worker检测是否是组播包,不是则Release包。
- 为了防止有的Worker线程被饿死,Worker线程并不是一次性处理完一个节点的所有包,而是按照他自己的权重值,处理一部分(最少一个)包就返回。
Monitor
基本单元
struct skynet_monitor {
int version;
int check_version;
uint32_t source;
uint32_t destination;
};
工作逻辑
skynet_monitor_trigger
自增version
。- 每个节点自带一个Monitor ,当它处理一个包的时候,就将这个包的信息利用
skynet_monitor_trigger
接口记录当前的source , destination
- 当Message处理完就利用
skynet_monitor_trigger
接口置source , destination
为0。 - 有个每五分钟执行一次的Monitor线程会检测所有的节点,如果
version
不等于check_version
, 就用version
赋值check_version
。 - 如果
version
等于check_version
且destination
不为0 , 则认为这个节点死循环,删除之
解析:
每五分钟记录并同之前记录比对检测当前的
version
(处理的包个数X2), 其实就是一个假定 :在5-10分钟内无法执行完的Message解析函数就是死循环。