一、创建若干 worker 线程
skynet 启动时,会创建若干 worker 线程(由配置指定)并指定线程权重:
static void
start(int thread) {
pthread_t pid[thread+3];
struct monitor *m = skynet_malloc(sizeof(*m));
memset(m, 0, sizeof(*m));
m->count = thread;
m->sleep = 0;
m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *));
int i;
for (i=0;i<thread;i++) {
m->m[i] = skynet_monitor_new();
}
if (pthread_mutex_init(&m->mutex, NULL)) {
fprintf(stderr, "Init mutex error");
exit(1);
}
if (pthread_cond_init(&m->cond, NULL)) {
fprintf(stderr, "Init cond error");
exit(1);
}
// 创建monitor线程
create_thread(&pid[0], thread_monitor, m);
// 创建timer线程
create_thread(&pid[1], thread_timer, m);
// 创建socket线程
create_thread(&pid[2], thread_socket, m);
static int weight[] = {
-1, -1, -1, -1, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, };
struct worker_parm wp[thread];
/*
创建若干worker线程,并指定线程权重
各worker线程每次会从global_mq中pop一条次级消息队列,并根据权重决定一次从次级消息队列消费多少条消息:
1)权重<0,worker线程一次消费一条消息(从次级消息队列中pop一个消息);
2)权重=0,worker线程一次消费次级消息队列里所有的消息;
3)权重>0,假设次级消息队列的长度为mq_length,将mq_length转成二进制数值后,向右移动weight(权重)位,结果即为worker线程一次从次级消息队列消费的消息数。
各worker线程对global_mq入/出队操作采用自旋锁,配上权重,大概是为了避免过多的worker线程为了等待spinlock解锁,而陷入阻塞状态
(因为一些线程,一次消费多条甚至全部次级消息队列的消息,因此在消费期间,不会对global_mq进行入队和出队操作,
注意:前四条线程,每次只从次级消息队列pop一条消息,这样做在一定程度上保证了没有服务会被饿死。
*/
for (i=0;i<thread;i++)
{
wp[i].m = m;
wp[i].id = i;
if (i < sizeof(weight)/sizeof(weight[0]))
{
wp[i].weight= weight[i]; // 前32个worker线程的权重由 weight[] 指定
}
else
{
wp[i].weight = 0; // 前32个之后的worker线程的权重为0
}
create_thread(&pid[i+3], thread_worker, &wp[i]);
}
for (i=0;i<thread+3;i++) {
pthread_join(pid[i], NULL);
}
free_monitor(m);
}
二、worker 线程消息处理
worker线程每次会从global_mq中pop一条次级消息队列(每条次级消息对应特定服务),并根据线程权重和次级消息队列的回调函数,对次级消息队列中的消息进行消费。处理完毕后,会从global_mq中再pop一条次级消息队列供下次调用,同时将本次使用的次级消息队列push回global_mq的尾部。
struct message_queue *skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight)
{
if (q == NULL)
{
//从global_mq中pop一条次级消息队列
q = skynet_globalmq_pop();
if (q==NULL)
return NULL;
}
//获取次级消息队列的回调函数
uint32_t handle = skynet_mq_handle(q);
struct skynet_context * ctx = skynet_handle_grab(handle);
if (ctx == NULL)
{
struct drop_t d = { handle };
skynet_mq_release(q, drop_message, &d);
//从global_mq中pop一条次级消息队列供下次调用
return skynet_globalmq_pop();
}
int i,n=1;
struct skynet_message msg;
for (i=0;i<n;i++)
{
// 从次级消息队列中pop一条消息
if (skynet_mq_pop(q,&msg))
{
skynet_context_release(ctx);
//从global_mq中pop一条次级消息队列供下次调用
return skynet_globalmq_pop();
}
else if (i==0 && weight >= 0)
{
//根据权重,设置本次应该从次级消息队列中消费多少条消息
n = skynet_mq_length(q);
n >>= weight;
}
// 判断次级消息队列是否满,最大容量1024
int overload = skynet_mq_overload(q);
if (overload)
{
skynet_error(ctx, "May overload, message queue length = %d", overload);
}
//处理消息
skynet_monitor_trigger(sm, msg.source , handle);
if (ctx->cb == NULL)
{
skynet_free(msg.data);
}
else
{
dispatch_message(ctx, &msg);
}
skynet_monitor_trigger(sm, 0,0);
}
assert(q == ctx->queue);
//从global_mq中pop一条次级消息队列供下次调用
struct message_queue *nq = skynet_globalmq_pop();
if (nq)
{
// 将本次使用的次级消息队列push回global_mq的尾部
// If global mq is not empty , push q back, and return next queue (nq)
// Else (global mq is empty or block, don't push q back, and return q again (for next dispatch)
skynet_globalmq_push(q);
q = nq;
}
skynet_context_release(ctx);
return q;
}
三、global_mq 出队
各worker线程采用自旋锁锁,每次会从global_mq中pop一条次级消息队列:
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;
}
四、global_mq 入队
将次级消息队列push回global_mq的尾部:
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)
}