skynet源码分析:消息

消息队列mq

消息队列是skynet的核心功能之一,它的功能说白了就是入队出队,先进先出,这个数据结构都有讲过。源码实现在skynet_mq.h和skynet_mq.c中。

skynet的消息队列实际上是有两种,一种是全局消息队列,一种是服务消息队列。每个服务都有自己的消息队列,每个服务消息队列中都有服务的handle标识。这个涉及到消息的派发,这里就不展开了。每个服务消息队列被全局消息队列引用。

全局消息队列用的是经典的链表来实现的,而服务的消息队列用的是比较不直观,可能对有些人来说理解起来特别困难的循环数组来实现的。而且数组空间不够的时候,会动态扩展,容量扩展为当前容量的2倍。

消息队列的出队入队函数名都比较简单而且明了,push/pop。这个名字可能会带来一定的误解,如果改成enqueue/dequeue的话,就更符合它的实际功能。

消息机制之消息处理

skynet的消息机制准备拆成三个部分来讲,第一部分是接收处理,第二部分是分发,第三部分是消息注册。

skynet是单进程多线程的,线程的种类有monitor/timer/socket/worker,monitor就是监控服务是不是陷入死循环了。timer是skynet自己实现的定时器。socket是负责网络的,这个应该是最容易被理解的。worker就是工作线程了,monitor/timer/socket都只有一个线程,唯独worker有多个线程,是可配的,不配的话是8个线程。每个工作线程有个叫worker_parm的参数。

在开始讲线程之前还需要回顾一下消息队列,在第2篇中讲过全局消息队列是链表,里面链了工作消息队列,而工作消息队列内部使用的是循环数组。
另外还要回顾一下消息的handle,每个服务都有运行时一个独一无二的handle,这个handle可以跟名字绑定。
接收处理

先总结一下,skynet_context_message_dispatch这个函数实际上就是不停地从全局消息队列里取工作队列,取到了以后呢,就一直处理这个队列里的消息。为了避免某个队列占用太多cpu,当前队列处理到一定的量,就把机会让给全局消息队列里的其它工作队列,把自己又放回全局消息队列。而这个处理的量是根据创建线程时thread_param里的weight权重来判定的,权重越大,流转的就越快,也就是说处理某个队列的消息数量就越少。这就是消息处理的主流程机制。

在主流程之外,还有monitor的触发和取消,每次处理前,触发monitor的检查。处理完了,取消monitor的检查。

 
分发
消息的处理实际上就是对工作队列里的消息不停地调回调函数。那么消息是怎么放进消息队列的呢。带着这个疑问,让我们从lua层开始追根溯源。

在lua层有两个api,一个是skynet.send,这个是非阻塞发消息。另一个是skynet.call,这个是阻塞式发完消息等回应。skynet.call使用一个session来实现等待,这个session实际就是一个自增的数字,溢出了以后又从1开始。

skynet.send实际上就是往目标服务的消息队列里增加一条消息。
注册

skynet的消息注册,C服务和lua服务设置回调走的函数是不同的。C的回调可以直接调,但是lua的回调不行,它需要一个默认的回调C函数,将返回参数转换为lua能理解的格式,遵循lua的api协议,传递到lua层。

当服务是lua实现的时候,skynet底层核心框架在处理完消息以后,回调lua层服务的回调函数时,要先经过一次lua api协议的处理,将参数准备好以后,然后调用lua服务中的回调函数。

参考:

《skynet_summary》

《Skynet框架之菜鸟手册》

skynet源码分析(2)--消息队列mq

猜你喜欢

转载自www.cnblogs.com/losophy/p/9203478.html