如果你接触过lua这种小巧的脚本语言,你就会经常接触到一个叫做协程的神奇概念。大多数脚本语言都有对协程不同程度的支持。但是大多编译语言,如C/C++,根本就不知道这样的东西存在。当然也很多人研究如何在编译语言实现协程的实现,轮子一个又一个的被发明。酷壳 这篇文章《一个“蝇量级” C 语言协程库》说的很详细,但对于文中介绍的协程库protothread,很难看的懂。风云大哥在搜索无满意结果后也重新发明轮子,实现自己版本的一个协程库《C 的 coroutine 库》, 风云版本的coroutine是常规的根据getcontext/swapcontext的非常传统的方法,没有用到其他什么奇淫技巧。同时他接口几乎和lua协程的接口一样,比较容易看的懂,但是貌似和CPU构架相关,下面详细说。这篇文章就是主要写看风云的代码的,代码注释放到github上面coroutine,当然因为代码本身很精练和简单,注释也就非常少的。关于协程,可参考百度百科:协程

  开始之前,看看unix/linux下面的context系列函数风云大哥说,windows下面可以用纤程实现,以前看《windows核心编程》的时候,了解过这个概念,但是实际上编程运用的只限于进程/线程,没有用到过纤程这高级货,现在接触windows api也相对少,所以这个就不详细展开)。context系列函数大约就以下函数:

    #include <ucontext.h>        

    int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp); void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

  根据linux手册页,getcontext返回的是当前执行进程的上下文信息,包括信号掩码,执行栈,寄存器等信息。比较值得注意的是makecontext,该函数除了传递ucp外,还传递一个通用的函数指针func和一个argc,传递给func的函数原型并不一定是void(*)()类型的,参数的个数由argc确定。当调用makecontext成功后,如果对这个上下文进行切换(swapcontext)或用这个上下文设置(setcontext),内核就会调用该func函数。协程的实现就是用到这个重要的原理。linux手册页有个详细的例子介绍makecontext怎么用的,请参考之。

  我们来看风云大哥的协程库:

    // 协程调度大小
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; // 用户数据
ucontext_t ctx; // 保存的协程上下文状态
struct schedule * sch; // 保存struct schedule指针
ptrdiff_t cap; // 上下文切换时保存的栈的容量
ptrdiff_t size; // 上下文切换时保存的栈的大小 size <= cap
int status; // 协程状态
char *stack; // 保存的协程栈大小
};

  定义的结构体够简洁的,除了struct coroutine里面的capsize,感觉作用不大。上下文信息是在coroutine_resume里面设置的,这里将栈设置为自己定义的区域,大小也限定了,还设置了下个上下文切换的地址(在swapcontext里为他赋初始值):

    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; // 下个切换的上下文状态,由swapcontext设置其值
S->running = id;
C->status = COROUTINE_RUNNING;
uintptr_t ptr = (uintptr_t)S;
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
swapcontext(&S->main, &C->ctx);

  风云大哥的协程库有点不好的地方就是这个协程库或许是与CPU的构架相关的,做成与CPU无关应该不难。看保存上下文信息的代码:

    static void
_save_stack(struct coroutine *C, char *top) {
char dummy = 0;
//这里做了一个特定的假设
// 栈由高地址向低地址方向增长,这种程序布局的CPU一般是X86构架的
// 所以这个库时与CPU结构相关的。
assert(top - &dummy <= STACK_SIZE); // stack大小
if (C->cap < top - &dummy) {
free(C->stack); // 首次C->stack为NULL,free(NULL)是OK的
C->cap = top-&dummy;
C->stack = malloc(C->cap);
}
C->size = top - &dummy;
memcpy(C->stack, &dummy, C->size);
}

  传递的参数top指向的是栈顶,栈顶与新定义的变量相减top - &dummy得到栈的大小。这里做了一个特定的假设,栈由高地址向低地址方向增长(这是非常典型的程序布局方式,虽然我不知道那种CPU不是用这种布局,但是觉得肯定有有低地址向高地址增长的),这种程序布局的CPU一般是X86构架的,所以这个库时与CPU结构相关的。

  总的来说,这个协程库写的非常精简。protothread这种高级货,暂时不看了。粗略的介绍完毕,如有错误,请批评指正。

  POST AT: http://luoguochun.cn/2014/08/21/coroutine/

coroutine协程的更多相关文章

  1. Coroutine(协程)模式与线程

    概念 协程(Coroutine)这个概念最早是Melvin Conway在1963年提出的,是并发运算中的概念,指两个子过程通过相互协作完成某个任务,用它可以实现协作式多任务,协程(coroutine ...

  2. Kotlin Coroutine(协程): 二、初识协程

    @ 目录 前言 一.初识协程 1.runBlocking: 阻塞协程 2.launch: 创建协程 3.Job 4.coroutineScope 5.协程取消 6.协程超时 7.async 并行任务 ...

  3. Kotlin Coroutine(协程): 一、样例

    @ 目录 前言 一.直接上例子 1.延时任务. 2.异步任务 3.并行任务: 4.定时任务: 总结 前言 你还在用 Hanlder + Message? 或者 AsyncTask? 你还在用 Rxja ...

  4. [Unity-22] Coroutine协程浅析

    1.概念解释 协程并非一个独立的线程.在Unity中.全部的语句都是在一个线程中运行的,也就是说.Unity是单线程的(详细的能够參见http://blog.csdn.net/alexander_xf ...

  5. Coroutine 协程

    https://en.wikipedia.org/wiki/Coroutine Coroutines are computer program components that generalize s ...

  6. 利用swoole coroutine协程实现redis异步操作

    <?php #注意:如果不开启兼容模式,会遇到这样的现象,用swoole协程的方法访问常规方法添加到redis中的数据,可能访问不到(直接返回NULL)!这可能是两者采用了不同的技术标准所致! ...

  7. Kotlin Coroutine(协程): 三、了解协程

    @ 目录 前言 一.协程上下文 1.调度器 2.给协程起名 3.局部变量 二.启动模式 CoroutineStart 三.异常处理 1.异常测试 2.CoroutineExceptionHandler ...

  8. Android中的Coroutine协程原理详解

    前言 协程是一个并发方案.也是一种思想. 传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高.但是面对计算密集型的任务不如多线程并行运算效率高. 不同的语言对于协程都有不同的实 ...

  9. Kotlin Coroutine(协程): 四、+ Retrofit

    @ 目录 前言 一.准备工作 二.开始使用 1.简单使用 2.DSL 3.扩展函数 4.请求发起 总结 前言 Retrofit 从 2.6.0 版本开始, 内置了对 Kotlin Coroutines ...

随机推荐

  1. C#将数据以XML格式写入Excel

    本文转载:http://www.cnblogs.com/eflylab/archive/2008/09/21/1295580.html c#将数据导入Excel另类方法 今天公司突然给个Excel模版 ...

  2. Seeking USB Serial Com Port in Windows Automatically : via PID VID

    After you read previous article, you might know how to operate a com port in Windows.    But that ex ...

  3. 常见的浏览器Hack技巧总结(转)

    如果你经常需要做前端页面,那么你一定多多少少需要解决页面的浏览器兼容问题.而浏览器兼容问题大部分也集中在对IE系列的兼容.这里就总结一下对IE系列的CSS Hack,记录一下,方便以后查阅. IE H ...

  4. android 15 activity跳转

    从一个屏幕跳到另一个屏幕,一个activity跳转到另一个activity,Intent类用于组件之间传递数据和跳转,组件包括不仅activity. package com.sxt.day04_01; ...

  5. centos安装g++

    安装: yum -y install gcc automake autoconf libtool make 安装g++: yum install gcc gcc-c++

  6. 再回首,Java温故知新(九):Java基础之流程控制语句

    流程控制语句分为条件语句.循环语句和中断语句. 中断语句包括break和continue,两者的区别在于break会跳出整个循环,而continue则是跳出此次循环,之后还会继续下一次循环. 条件语句 ...

  7. oracle多表关联删除数据表记录方法

    oracle多表关联删除的两种方法 第一种使用exists方法 delete from tableA where exits ( select 1 from tableB Where tableA.i ...

  8. android 利用隐式Intent打开图片

    实现功能   点击"查看图片"时能够跳出提示,选择系统图库打开还是自己编写的应用打开,并且对于下载好的图片也有效. 1.我将 qiaoba.jpg 放在 res/drawable  ...

  9. 前端过滤XSS攻击

    日常开发过程中,对于存在用户交互的一些门户网站等,过滤xss攻击是必不可少的. 此处主要记录下我在工作过程中的简单处理方法. 前端过滤XSS攻击, 我这里用的是开源工程 js-xss,官网地址:htt ...

  10. oracle备份表

    oracle与sql单表备份的区别 (     oracle中备份表: create table 备份表名 as select * from 原表 sql server中备份表: select * i ...