由于本源代码蛮长的,所以按照功能划分模块来分析,分为若干部分,详见二级目录↑
代码文件:co_routine.h,co_routine.cpp,co_routine_inner.h
三、协程各种操作
void co_yield_env( stCoRoutineEnv_t *env );//将当前执行协程从协程栈中出栈并将执行权交给栈内上一个协程。
void co_yield_env( stCoRoutineEnv_t *env )
{
stCoRoutine_t *last = env->pCallStack[ env->iCallStackSize - 2 ];
stCoRoutine_t *curr = env->pCallStack[ env->iCallStackSize - 1 ];
env->iCallStackSize--;
co_swap( curr, last);
}
//切出到主协程
void co_yield_ct()
{
co_yield_env( co_get_curr_thread_env() );
}
//跟co_yield_env功能一样只是参数不同,编程时常用。
void co_yield( stCoRoutine_t *co )
{
co_yield_env( co->env );
}
static int CoRoutineFunc( stCoRoutine_t *co,void * );//被co_resume作为协程入口函数,可以做一些协程流程基本信息的填写,比如是否是第一次执行、协程退出后执行权的交接。
函数内调用了pfn函数(pfn函数就是协程的主函数)
执行完毕后cEnd标记为1
这里修改了子协程的env就完成了出栈工作,env是全局共享的。
static int CoRoutineFunc( stCoRoutine_t *co,void * )
{
if( co->pfn )
{
co->pfn( co->arg );//执行主函数,内部允许嵌套有限数量的协程调用
}
co->cEnd = 1;//执行完毕后标记为1
stCoRoutineEnv_t *env = co->env;//env是指针,修改后co的原内容也同步改变。其实下面可以直接写co->env,因为下面的函数没有改变env指针值。
co_yield_env( env );//将当前执行的co从协程栈中出栈并将执行权交给父协程。
return 0;
}
void co_free( stCoRoutine_t *co );//负责协程销毁工作。
void co_free( stCoRoutine_t *co )
{
if (!co->cIsShareStack)
{
free(co->stack_mem->stack_buffer);
free(co->stack_mem);
}
free( co );
}
void co_release( stCoRoutine_t *co );//销毁co
void co_release( stCoRoutine_t *co )//销毁co但是不销毁用户栈
{
co_free( co );
}
void save_stack_buffer(stCoRoutine_t* occupy_co);//用于备份用户栈有效数据。
void save_stack_buffer(stCoRoutine_t* occupy_co)
{
///copy out
stStackMem_t* stack_mem = occupy_co->stack_mem;//取用户栈
int len = stack_mem->stack_bp - occupy_co->stack_sp;//计算有效数据长度
if (occupy_co->save_buffer)
{
free(occupy_co->save_buffer), occupy_co->save_buffer = NULL;
}
occupy_co->save_buffer = (char*)malloc(len); //malloc buf;
occupy_co->save_size = len;
memcpy(occupy_co->save_buffer, occupy_co->stack_sp, len);
}
交换cpu上执行的协程。
如果协程使用了共享栈,则在必要的时候保存和还原栈内数据。
void co_swap(stCoRoutine_t* curr, stCoRoutine_t* pending_co)
{
stCoRoutineEnv_t* env = co_get_curr_thread_env();
//get curr stack sp
char c;
curr->stack_sp= &c; //取当前栈顶,局部变量的地址就是栈顶喽,然后本函数后面也用了栈,不过函数过后丢掉就好了,不用保存。
if (!pending_co->cIsShareStack) //不使用share stack自然就不存在当前占用共享栈的协程和即将占用共享栈的协程。
{ //见官方注释:for copy stack log lastco and nextco
env->pending_co = NULL;
env->occupy_co = NULL;
}
else //occupy_co是原来share stack的所有者,pending要抢占share stack (多个协程争抢同一块share stack)
{
env->pending_co = pending_co;//设置好pending_co,下文中会将env->pending_co的栈还原
//get last occupy co on the same stack mem
stCoRoutine_t* occupy_co = pending_co->stack_mem->occupy_co;//公共栈空间原来的所有者
//(occupy_co初始化是NULL),第一次执行协程后这里就会把它变为自己。初始化后第一次拿到执行权栈不为空之后,它不再会是NULL。
//set pending co to occupy thest stack mem;
pending_co->stack_mem->occupy_co = pending_co; //不管之前是谁占用了这个地盘,pending都会抢占share stack
env->occupy_co = occupy_co; //记录下之前是谁在使用share stack
//如果pending要抢占share stack 那原来的所有者
if (occupy_co && occupy_co != pending_co)//有occupy_co并且不是自己
{
save_stack_buffer(occupy_co);//将occupy_co的栈中有效数据保存到occupy->save_buffer中
//pending_co把occupy_co撵走之后就可以还原自己的栈空间了。
}
}
//swap context
coctx_swap(&(curr->ctx),&(pending_co->ctx));
//stack buffer may be overwrite, so get again; //why?
stCoRoutineEnv_t* curr_env = co_get_curr_thread_env();
stCoRoutine_t* update_occupy_co = curr_env->occupy_co;
stCoRoutine_t* update_pending_co = curr_env->pending_co;
if (update_occupy_co && update_pending_co && update_occupy_co != update_pending_co)
{
//resume stack buffer
if (update_pending_co->save_buffer && update_pending_co->save_size > 0)
{
memcpy(update_pending_co->stack_sp, update_pending_co->save_buffer, update_pending_co->save_size);
}//pending抢占share stack
}
}