skynet worker 线程

一、创建若干 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)
}
发布了112 篇原创文章 · 获赞 22 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/u010601662/article/details/105482494