skynet底层源码阅读(2)-消息队列

来源:互联网 发布:excel不显示重复数据 编辑:程序博客网 时间:2024/05/22 13:34

skynet的c底层源码在Skynet-src中。一开始,我们首先从一些简单的组件看起。skynet主要用于处理服务间的消息,每个服务都有存放消息的队列。在Skynet_mq.h中,定义了消息队列中的消息类型:

// sky_net 内部的消息struct skynet_message {uint32_t source;//消息源的句柄int session;//用来做上下文的标识void * data;//消息指针size_t sz;//消息长度,消息的请求类型定义在高八位};

skynet的每一个服务都有自己的编号,source就表示该消息来自哪个编号的服务。

在Skynet_mq.c中,定义了消息队列结构体:

struct message_queue {//锁struct spinlock lock; uint32_t handle;  //所属服务handleint cap;  //容量int head;  //对头 int tail;  //队尾//消息队列释放标记,当要释放一个服务的时候,清理标记//不能立即释放该服务对应的消息队列(有时候工作线程还在操作mq),就需要设置一个标记,标记是否可以释放int release; //该消息队列是否在全局的消息队列中int in_global;int overload;int overload_threshold;//消息队列  利用数组实现的一个循环队列  struct skynet_message *queue;//为了将该队列串联到全局的二维的消息队列中struct message_queue *next;};
queue指向存放数据的内存,该结构体主要管理了一个循环队列,实现比较简单。其余的操作无非就是将消息加入队列,弹出队列。
//将消息加入到message_queue 的 queue的尾部void skynet_mq_push(struct message_queue *q, struct skynet_message *message) {assert(message);SPIN_LOCK(q)q->queue[q->tail] = *message;if (++ q->tail >= q->cap) {q->tail = 0;}//扩容if (q->head == q->tail) {expand_queue(q);}if (q->in_global == 0) {//标记为在全局的二维消息队列中q->in_global = MQ_IN_GLOBAL;//将消息队列加入到全局的消息队列中 skynet_globalmq_push(q);}SPIN_UNLOCK(q)}

在消息队列容量不够的时候,会扩展容量:

//将消息队列message_queue   的queue容量扩大两倍static voidexpand_queue(struct message_queue *q) {struct skynet_message *new_queue = skynet_malloc(sizeof(struct skynet_message) * q->cap * 2);int i;//复制以前的for (i=0;i<q->cap;i++) {new_queue[i] = q->queue[(q->head + i) % q->cap];}q->head = 0;q->tail = q->cap;q->cap *= 2;//销毁以前的 skynet_free(q->queue);q->queue = new_queue;}

//从循环消息队列message_queue中取出一条消息struct skynet_message   message是传出参数intskynet_mq_pop(struct message_queue *q, struct skynet_message *message) {int ret = 1;//加锁SPIN_LOCK(q)//如果循环消息队列中存在消息if (q->head != q->tail) {//传出一个消息*message = q->queue[q->head++];ret = 0;int head = q->head;int tail = q->tail;int cap = q->cap;if (head >= cap) {q->head = head = 0;}//获得循环消息队列中的消息个数int length = tail - head;if (length < 0) {length += cap;}while (length > q->overload_threshold) {q->overload = length;q->overload_threshold *= 2;}} else {// reset overload_threshold when queue is emptyq->overload_threshold = MQ_OVERLOAD;}//如果循环消息队列中没有消息,=0标记该循环队列没有在全局的二维消息队列中if (ret) {q->in_global = 0;}SPIN_UNLOCK(q)return ret;}
为了统一管理每个服务的消息队列,skynet将没有服务的消息队列链接起来,形成一个二级的消息队列:
struct global_queue {struct message_queue *head;struct message_queue *tail;//锁struct spinlock lock;};

//全局变量static struct global_queue *Q = NULL;


也有对全局消息队列的添加与移除操作:

//将消息队列插入到全局消息队列中void skynet_globalmq_push(struct message_queue * queue) {struct global_queue *q= Q;SPIN_LOCK(q)assert(queue->next == NULL);//插入到尾部if(q->tail) {q->tail->next = queue;q->tail = queue;} else {//第一次插入的时候q->head = q->tail = queue;}SPIN_UNLOCK(q)}
//从全局队列中   移除一个消息队列message_queue,从头部移除,返回该指针struct message_queue * skynet_globalmq_pop() {struct global_queue *q = Q;SPIN_LOCK(q)struct message_queue *mq = q->head;//从头部移除if(mq) {q->head = mq->next;if(q->head == NULL) {assert(mq == q->tail);q->tail = NULL;}mq->next = NULL;}SPIN_UNLOCK(q)return mq;}

其余的就是一些创建和销毁的函数了,实现比较简单。整体结构如图: