skynet创建的服务通过消息进行驱动,而消息的获取是通过队列。skynet有个全队列,其结构拥有一个头尾指针,并且有个自旋锁,防止push进来的数据过多。
// 全局队列:每个结点是一个消息队列指针,指向服务的消息队列
struct global_queue {
struct message_queue *head;
struct message_queue *tail;
struct spinlock lock;
};
其中的message_queue则是消息队列,其结构如下,当创建一个服务的时候,会场景一个消息队列,并返回给ctx,创建成功后会将该消息队列push进全局消息队列中,这在上面也是有提到的。消息处理也可以看上一节的worker线程的处理。
struct message_queue {
struct spinlock lock;
uint32_t handle; // 关联的服务句柄
int cap; // 队列容量
int head; // 队列头的位置:用数组模拟环形的队列
int tail; // 队列尾的位置
int release; // 标记该队列是否为释放状态,1为释放
int in_global; // 标识是否在全局队列中,见MQ_IN_GLOBAL
int overload; // 过载的消息数量
int overload_threshold; // 过载的阀值,被初始化为MQ_OVERLOAD
struct skynet_message *queue; // 消息结构数组
struct message_queue *next; // 指向下一个消息队列
};
那么消息是如何写到消息队列中的呢,即通过skynet.send()将消息push进的,流程如下:通过调用skynet.send(addr, type, …)进而调用lua-skynet的send_message函数。紧接着向服务压入该消息。根据句柄找到对应的ctx,然后将该ctx的消息队列以及消息作为参数调用skynet_mq_push,从而达到想改消息队列push消息的目的。次级消息队列的内容,并不是context结构的一部分(context只是引用了他的指针),因此,在一个服务执行callback的同时,其他服务(可能是多个线程内执行callback的其他服务)可以向它的消息队列里push消息,而mq的push操作,是加了一个自旋锁,以避免多个线程,同时操作一个消息队列。