C/C++协程学习笔记丨C/C++实现协程及原理分析视频( 二 )


  1. 首先调用c.next()启动生成器;
  2. 然后 , 一旦生产了东西 , 通过c.send(n)切换到consumer执行;
  3. consumer通过yield拿到消息 , 处理 , 又通过yield把结果传回;
  4. produce拿到consumer处理的结果 , 继续生产下一条消息;
  5. produce决定不生产了 , 通过c.close()关闭consumer , 整个过程结束 。
整个流程无锁 , 由一个线程执行 , produce和consumer协作完成任务 , 所以称为“协程” , 而非线程的抢占式多任务 。
协程视频视频以及文档资料后台私信【协程】获取
C/C++协程学习笔记丨C/C++实现协程及原理分析视频文章插图
C/C++协程学习笔记丨C/C++实现协程及原理分析视频文章插图
C/C++ 协程首先需要声明的是 , 这里不打算花时间来介绍什么是协程 , 以及协程和线程有什么不同 。 如果对此有任何疑问 , 可以自行 google 。 与 Python 不同 , C/C++ 语言本身是不能天然支持协程的 。 现有的 C++ 协程库均基于两种方案:利用汇编代码控制协程上下文的切换 , 以及利用操作系统提供的 API 来实现协程上下文切换 。 典型的例如:
  • libco , Boost.context:基于汇编代码的上下文切换
  • phxrpc:基于 ucontext/Boost.context 的上下文切换
  • libmill:基于 setjump/longjump 的协程切换
一般而言 , 基于汇编的上下文切换要比采用系统调用的切换更加高效 , 这也是为什么 phxrpc 在使用 Boost.context 时要比使用 ucontext 性能更好的原因 。 关于 phxrpc 和 libmill 具体的协程实现方式 , 以后有时间再详细介绍 。
libco 协程的创建和切换在介绍 coroutine 的创建之前 , 我们先来熟悉一下 libco 中用来表示一个 coroutine 的数据结构 , 即定义在 co_routine_inner.h 中的 stCoRoutine_t:
struct stCoRoutine_t
{
stCoRoutineEnv_t *env; // 协程运行环境
pfn_co_routine_t pfn; // 协程执行的逻辑函数
void *arg; // 函数参数
coctx_t ctx; // 保存协程的下文环境
...
char cEnableSysHook; // 是否运行系统 hook , 即非侵入式逻辑
char cIsShareStack; // 是否在共享栈模式
void *pvEnv;
stStackMem_t* stack_mem; // 协程运行时的栈空间
char* stack_sp; // 用来保存协程运行时的栈空间
unsigned int save_size;
char* save_buffer;
};
我们暂时只需要了解表示协程的最简单的几个参数 , 例如协程运行环境 , 协程的上下文环境 , 协程运行的函数以及运行时栈空间 。 后面的 stack_sp , save_size 和 save_buffer 与 libco 共享栈模式相关 , 有关共享栈的内容我们后续再说
协程创建和运行由于多个协程运行于一个线程内部的 , 因此当创建线程中的第一个协程时 , 需要初始化该协程所在的环境 stCoRoutineEnv_t , 这个环境是线程用来管理协程的 , 通过该环境 , 线程可以得知当前一共创建了多少个协程 , 当前正在运行哪一个协程 , 当前应当如何调度协程:
struct stCoRoutineEnv_t
{
stCoRoutine_t *pCallStack[ 128 ]; // 记录当前创建的协程
int iCallStackSize; // 记录当前一共创建了多少个协程
stCoEpoll_t *pEpoll; // 该线程的协程调度器
// 在使用共享栈模式拷贝栈内存时记录相应的 coroutine
stCoRoutine_t* pending_co;
stCoRoutine_t* occupy_co;
};
上述代码表明 libco 允许一个线程内最多创建 128 个协程 , 其中 pCallStack[iCallStackSize-1] 也就是栈顶的协程表示当前正在运行的协程 。 当调用函数 co_create 时 , 首先检查当前线程中的 coroutine env 结构是否创建 。 这里 libco 对于每个线程内的 stCoRoutineEnv_t 并没有使用 thread-local 的方式(例如gcc 内置的 __thread , phxrpc采用这种方式)来管理 , 而是预先定义了一个大的数组 , 并通过对应的 PID 来获取其协程环境 。 :


推荐阅读