基于libco的c++协程实现2(libco源码-示例及协程创建)

对于一个新东西,应该怎么学?

1、如果是一个你没接触过的,我建议第一步,看下官方的说明,跑一下官方的demo

2、如果是一个你熟悉的,那就撸起袖子开始干,直接跳过demo。

我先以一个libco的简单demo example_cond.cpp,,来看看libco是怎么来用的

​void* Producer(void* args)
{
	co_enable_hook_sys();
	stEnv_t* env=  (stEnv_t*)args;
	int id = 0;
	while (true)
	{
		stTask_t* task = (stTask_t*)calloc(1, sizeof(stTask_t));
		task->id = id++;
		env->task_queue.push(task);
		printf("%s:%d produce task %d\n", __func__, __LINE__, task->id);
		co_cond_signal(env->cond);
		poll(NULL, 0, 1000);
	}
	return NULL;
}

void* Consumer(void* args)
{
	co_enable_hook_sys();
	stEnv_t* env = (stEnv_t*)args;
	while (true)
	{
		if (env->task_queue.empty())
		{
			co_cond_timedwait(env->cond, -1);
			continue;
		}
		stTask_t* task = env->task_queue.front();
		env->task_queue.pop();
		printf("%s:%d consume task %d\n", __func__, __LINE__, task->id);
		free(task);
	}
	return NULL;
}

int main()
{
	stEnv_t* env = new stEnv_t;
	env->cond = co_cond_alloc();
    // 先创建了消费者
	stCoRoutine_t* consumer_routine;
	co_create(&consumer_routine, &attr, Consumer, env);
	co_resume(consumer_routine);
    // 再传建了生产者
	stCoRoutine_t* producer_routine;
	co_create(&producer_routine, &attr, Producer, env);
	co_resume(producer_routine);
	// libco的事件循环
	co_eventloop(co_get_epoll_ct(), NULL, NULL);
	return 0;
}

​

输出的结果:

 可以看出,不需要启动任何线程,只需要主线程,就能实现一个协程发,一个协程收,这在没有协程的程序,是不是感觉应该做不到!

demo中用到了哪些函数

从上面的demo,我们可以看到,他用到了几个关键的函数,也就是这几个关键的函数,来为我们实现了整个协程的框架通信和切换

1、co_create :创建一个协程

2、co_resume :启动一个协程

3、co_eventloop :协程调度事件循环

4、poll:等待并让出cpu资源 co_cond_signal:协程信号量 co_cond_timedwait:等待信号量。

co_create实现

创建一个协程需要些什么东西:

 接口:

int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr,pfn_co_routine_t pfn,void *arg )

 从入参我们可以看出,需要协程的属性attr,协程的回调函数pfn,以及协程的参数arg,这个跟线程的创建的参数很像。

先看看属性需要啥?

共享栈的指针,共享栈的大小,说明如果是设置共享栈,还需要外部把栈创建好传进来的。这里有个印象就行,栈大小是指保存的栈多大,默认128k。

其中__attribute__ ((packed)) 就是结构体的对齐方式使用1字节对齐,也就是不会根据结构体成员来选择对齐的方式

struct stCoRoutineAttr_t
{
	int stack_size;					// 栈大小
	stShareStack_t*  share_stack;	// 共享栈指针
	stCoRoutineAttr_t()
	{
		stack_size = 128 * 1024;
		share_stack = NULL;
	}
}__attribute__ ((packed)); // 一字节对齐

 实现 

	if( !co_get_curr_thread_env() ) 
	{
		// 创建了主协程,创建了gCoEnvPerThread 线程全局环境变量,只运行一次
		co_init_curr_thread_env();
	}

	// 创建子协程
	stCoRoutine_t *co = co_create_env( co_get_curr_thread_env(), attr, pfn, arg );
	*ppco = co;
	return 0;

可以看出,创建子协程需要用到,子协程接口

struct stCoRoutine_t *co_create_env( stCoRoutineEnv_t * env, const stCoRoutineAttr_t* attr,pfn_co_routine_t pfn,void *arg )

其中需要的结构:

struct stCoRoutineEnv_t
{
	stCoRoutine_t *pCallStack[ 128 ];		// 调用栈,第0个是主协程
	int iCallStackSize;						// 主协程+1  resume +1 yield -1
	stCoEpoll_t *pEpoll;

	//for copy stack log lastco and nextco // 为共享栈准备的,独立栈为空
	stCoRoutine_t* pending_co;
	stCoRoutine_t* occupy_co;
};

其中,有一个调用栈的数组,pCallStack,这个的0下标应该是主协程

而在创建子协程时,这个结构还没有初始化,所以创建子协程之前,需要co_init_curr_thread_env(),再来看看他的实现

// 线程的全局变量,每个线程独立,是基于单一线程全局的变量,因为没有该关键字,就变成了进程的全局变量
static __thread stCoRoutineEnv_t* gCoEnvPerThread = NULL;

void co_init_curr_thread_env()
{
	gCoEnvPerThread = (stCoRoutineEnv_t*)calloc( 1, sizeof(stCoRoutineEnv_t) );
	stCoRoutineEnv_t *env = gCoEnvPerThread;

	// 这是创建一个主协程
	struct stCoRoutine_t *self = co_create_env( env, NULL, NULL,NULL );
	self->cIsMain = 1;
	// 清空coctx,主协程没有,为啥不完全在co_create_env里面初始化,因为主协程的特殊性
	coctx_init( &self->ctx );

	// 这些无用功,上面已经calloc了
	env->iCallStackSize = 0;
	env->pending_co = NULL;
	env->occupy_co = NULL;
	// 把全局的env的第一个调用栈给了主协程
	env->pCallStack[env->iCallStackSize++] = self;

	// epoll 初始化
	stCoEpoll_t *ev = AllocEpoll();
	SetEpoll( env, ev );
}

主要的代码注释都写了,基本上应该没什么阅读上的难度。其中有一个变量gCoEnvPerThread,是一个__thread 类型,这是一个线程的相关的局部变量,每个线程不同,协程共享。

再看看真正的创建子协程的实现

struct stCoRoutine_t *co_create_env( stCoRoutineEnv_t * env, const stCoRoutineAttr_t* attr,
		pfn_co_routine_t pfn,void *arg )
{
	stCoRoutineAttr_t at;
	if( attr )
	{
		memcpy( &at, attr, sizeof(at));
	}

	// 1. 获取要分配的存放栈的堆大小
	if( at.stack_size <= 0 ) // 默认128K
	{
		at.stack_size = 128 * 1024;
	}
	else if( at.stack_size > 1024 * 1024 * 8 ) // >8M 设置8M
	{
		at.stack_size = 1024 * 1024 * 8;
	}
	if( at.stack_size & 0xFFF ) // 大于4K的,不对齐,则4K对齐,内存的页是4k对齐,这样可以减少内存碎片
	{
		at.stack_size &= ~0xFFF;
		at.stack_size += 0x1000;
	}

	// 2.如果外部应用co_alloc_sharestack创建了共享栈,则从共享栈直接获取,否则根据stack_size分配
	stStackMem_t* stack_mem = NULL;
	if (at.share_stack) 
	{
		stack_mem = co_get_stackmem(at.share_stack);
		at.stack_size = at.share_stack->stack_size;
	}
	else
	{
		stack_mem = co_alloc_stackmem(at.stack_size);
	}

	// 3.创建协程对象并初始化,这里的顺序被我调了
	stCoRoutine_t *lp = (stCoRoutine_t*)malloc( sizeof(stCoRoutine_t) );
	memset( lp, 0, (long)(sizeof(stCoRoutine_t))); 
	lp->env = env;
	lp->pfn = pfn;
	lp->arg = arg;

	lp->stack_mem = stack_mem;
	lp->ctx.ss_sp = stack_mem->stack_buffer;
	lp->ctx.ss_size = at.stack_size;
	
	lp->cStart = 0;
	lp->cEnd = 0;
	lp->cIsMain = 0;
	lp->cEnableSysHook = 0;
	lp->cIsShareStack = at.share_stack != NULL;
	lp->save_size = 0;
	lp->save_buffer = NULL;

	return lp;
}

这里我调整了官方的顺序,效果完全一样,方便大家阅读,其中分为三步,每一步对变量进行初始化。lp是真正创建出来的协程实体,其各个变量定义如下

struct stCoRoutine_t
{
	stCoRoutineEnv_t *env;			// 线程独有的参数
	pfn_co_routine_t pfn;			// 协程函数
	void *arg;						// 协程参数
	coctx_t ctx;					// 保存协程cpu上下文

	char cStart;					// 启动
	char cEnd;						// 结束让出
	char cIsMain;					// 标记是否是主协程
	char cEnableSysHook;			// 是否使能hook
	char cIsShareStack;				// 是否是共享栈

	void *pvEnv;					// 系统环境变量的键值对

	//char sRunStack[ 1024 * 128 ];
	stStackMem_t* stack_mem;		// 本协程对应的栈内存

	//save satck buffer while confilct on same stack_buffer;
    //如果切入和切出的协程不同,则需要保存
	char* stack_sp;					// 作为保存共享栈栈顶的指针
	unsigned int save_size;         // 共享栈buffer的大小
	char* save_buffer;              // 共享栈栈底的堆的初始位置

	stCoSpec_t aSpec[1024];
};

到这里,协程的创建完成。协程的启动在下一个文章分享

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值