前言

前段时间研读云风的coroutine库,为了加深印象,做个简单的笔记。不愧是大神,云风只用200行的C代码就实现了一个最简单的协程,代码风格精简,非常适合用来理解协程和用来提升编码能力。

协程简介

协程是用同步的写法达到异步的性能。其基本原理是在IO等待时切换出去,在适当的时刻切换回来,最大程度利用CPU。协程可以理解为一个用户级的线程,一个线程里跑多个协程。并且,不管协程数量多少,都是串行运行的,就是说不存在同一时刻属于一个线程的不同协程同时运行。因此避免了多线程编程可能导致的同步问题。

协程的行为有点像函数调用,但也有不同,对于函数调用来说,假如函数A调用函数B,则必须等待函数B执行完毕后才能重新返回A,但对于协程来说,如果再协程A中切换到协程B,协程B可以选择在某个点重新回到A的执行流,同时允许在某个时刻重新从A回到B之前运行到的那个点。这在函数中是不可能实现的,因为函数只能一路走到底。

ucontext实现协程切换

既然允许协程中途中切换以及后期从新从切换点进入继续执行,说明必须有数据结构保存每个协程的上下文信息。云风携程库运用Linux中的ucontext实现协程见切换,而Linux包含以下几个系统函数对ucontext_t进行初始化、设置,以及基于ucontext_t切换协程:

getcontext()  : 获取当前context
setcontext() : 切换到指定context
makecontext() : 设置函数指针和堆栈到对应context保存的sp和pc寄存器中,调用之前要先调用 getcontext()
swapcontext() : 保存当前context,并且切换到指定context

主要数据结构

源码设计的数据结构有如下两个,coroutine协程保存自身的上下文信息、主体函数和栈信息等。每个协程需要自己主动让出CPU,至于交给谁处理,由schedule调度器决定,调度器管理协程,包括保存和切换协程。

struct schedule {
char stack[STACK_SIZE]; //栈空间
ucontext_t main; //当前上下文
int nco; //协程数
int cap; //协程容量
int running; //是否正在运行
struct coroutine **co; //协程数组
}; struct coroutine {
coroutine_func func; //协程运行主体函数
void *ud; //func的参数
ucontext_t ctx; //该协程的上下文信息
struct schedule *sch; //对应的调度器
ptrdiff_t cap; //协程大小
ptrdiff_t size; //协程实际大小
int status; //运行状态
char *stack; //栈
};

保存现场

协程从运行状态COROUTINE_RUNNING到暂停状态COROUTINE_SUSPEND时需要保存运行栈,即调用coroutine_yield 之后挂起协程让出CPU的过程。下面是保存栈方法:

static void
_save_stack(struct coroutine *C, char *top) {
//获取当前栈底,top 是栈顶,top-dummy 即该协程的私有栈空间
char dummy = ;
assert(top - &dummy <= STACK_SIZE);
//如果协程私有栈空间大小不够放下运行时的栈空间,则要重新扩容
if (C->cap < top - &dummy) {
free(C->stack);
C->cap = top-&dummy;
C->stack = malloc(C->cap); //每一个协程都会开辟这块栈空间
}
C->size = top - &dummy;
memcpy(C->stack, &dummy, C->size);
}

top 代表当前协程运行栈的栈顶,从 coroutine_yield 我们知道 top = S->stack + STACK_SIZE,原因是协程初始化时设置如下:

getcontext(&C->ctx);
C->ctx.uc_stack.ss_sp = S->stack;
C->ctx.uc_stack.ss_size = STACK_SIZE;
C->ctx.uc_link = &S->main;
makecontext(&C->ctx, (void (*)(void)) mainfunc, , (uint32_t)ptr, (uint32_t)(ptr>>));

即表示协程栈的栈顶设置为S->stack,mainfunc的运行使用S->stack作为栈顶,大小为STACK_SIZE,由此可见,schedule 的 stack[STACK_SIZE] 是子协程运行的公共栈空间,但是每个协程的栈不一样,所以需要单独建立一个私有栈空间来保存执行现场。

https://blog.csdn.net/u011228889/article/details/79759834

最新文章

  1. 详解树莓派Model B+控制蜂鸣器演奏乐曲
  2. 总结ThinkPHP使用技巧经验分享(一)
  3. 初学Python之谈
  4. 使用Word发布文章到 WordPress 博客
  5. 认识ATL窗口
  6. 后台跳转到登录页嵌套在iframe的问题(MVC例)
  7. 洛谷P1736 创意吃鱼法
  8. SVN 记录冲突、忽略
  9. PHPCMS标签:PC标签模板语法规则
  10. 九度OnlineJudge之1022:游船出租
  11. dapper 可空bool转换出错及解决方案
  12. Mybatis源码分析-SqlSessionTemplate
  13. Apache和Tomcat整合(一个Apache 不同域名处理多个不同业务)
  14. App 基本图片配置(I)
  15. 高仿MT4行情终端(K线图+操控+简单架构)
  16. underrun || overrun
  17. Lock的lockInterruptibly()方法
  18. (zhuan) 资源|TensorFlow初学者必须了解的55个经典案例
  19. Lua在Windows下的配置、安装、运行
  20. 编译lua-5.3.5时出错解决方法

热门文章

  1. 数据库连接windows身份验证、sql验证
  2. Vue3.0+TypeScript
  3. Binary Stirling Numbers
  4. vue刷新子页面,跳到主页,params传参引起的血案!
  5. Vue_(组件通讯)非父子关系组件通信
  6. Vue_(组件)自定义指令
  7. NDK OpenGLES3.0 开发(五):FBO 离屏渲染
  8. Eclipse项目迁移到MyEclipse后,继承HttpServlet报错问题
  9. python3笔记二十:时间操作time
  10. SRS之SrsConfig类