使用ucontext组件实现的coroutine代码分析
coroutine一般翻译过来就是协程,类似于线程可以切换,而跟线程是由操作系统调度器来实现切换不一样,协程由用户程序自己调度进行切换。我以前也看过协程相关的内容,但没有自己去实现过。最近搞OpenStack,OpenStack各个模块都是单线程模型,但是用了eventlet的绿色线程,eventlet也是Python的协程实现库。这篇文章我并不打算剖析Python协程库的实现,而是分析一个基于Linux下ucontext组件的C语言实现,原作者是云风,我以前也看过这个实现,只是现在忘了,没有自己写过或者分析过代码,只是看看好像永远是似懂非懂。后来yanyiwu又fork了一个实现并做些修改,据说更易懂,我就直接拿他修改后的版本分析就ok了,这里对他们表示感谢。
这个简单的实现包含三个文件,分别是头文件coroutine.h,协程实现文件coroutine.c和测试主程序main.c,我给代码加了点注释,并编译运行。
coroutine.h源码:
coroutine.h里面是一些宏定义和函数声明:
coroutine_func:一个函数指针,声明了coroutine的函数原型;
coroutine_open:要使用该协程库时第一个被调用的函数,它返回一个调度器结构体;
coroutine_close:关闭协程调度器,最后被调用不解释;
coroutine_new:将一个函数还有需要传递的参数加入到协程的调度器里边;
coroutine_yield:退出当前运行的协程;
coroutine_resume:恢复具有特定id值的协程;
coroutine_running:返回正在运行的协程id,-1表示没有正在运行的协程;
schedule_status:返回1表示还有等待运行的协程,返回0表示所有协程都已运行完毕;
主要的实现都在coroutine.c文件,源码如下:
对coroutine.c源码我们暂时不作分析,一会儿分析main.c时自然会讲到它。
main.c源码如下:
我们来分析下main.c的代码。先看下main函数,调用了coroutine_open函数,返回一个调度器结构体,然后调用test函数并把调度器结构体当作参数,最后调用coroutine_close函数关闭调度器。显然,test函数就是接脏活累活的地方了。看下test函数里的35,36行,调用了coroutine_new创建两个协程,分别使用了函数foo和foo2,参数分别为arg1和arg2,并返回了协程id,分别为co1和co2。接着是一个while循环,看下代码:
while (schedule_status(S)) {
coroutine_resume(S,co1);
coroutine_resume(S,co2);
}
可以看出,当schedule_status返回为1时,将对协程co1和co2分别调用 coroutine_resume函数,schedule_status返回0时test函数退出。这回,我们不得不去看coroutine_resume函数了:
coroutine_resume函数有两个参数,分别为调度器结构体和协程id。该函数首先根据协程id从调度器中获取对应的协程结构体,然后对状态status作判断,可能的状态为COROUTINE_READY和COROUTINE_SUSPEND。
status为COROUTINE_READY(协程第一次被调度)时:
调用getcontext获取当前(注意,当前不是传进来id所对应的协程)协程的上下文,保存在传进来的id所对应的协程结构体中类型为ucontext_t的变量ctx,接着修改ctx结构体的栈指针和栈大小,并把该协程退出时要执行的协程上下文设置成调度器结构体内类型为ucontext_t的变量main,然后将调度器结构体里running变量设置为要将要执行的协程的id,将要执行的协程的状态status设置为COROUTINE_RUNNING,再调用makecontext修改要执行协程上下文,参数为要执行的协程上下文变量、mainfunc函数地址、mainfunc参数个数、给mainfunc传递的参数,因此后续该协程执行时,就会调用mainfunc函数,最后调用swapcontext,该函数将当前协程的上下文内容保存在调度器结构体的main变量中,并激活要执行的协程上下文,于是mainfunc函数被调用了。
status为COROUTINE_SUSPEND时:
将调度器结构体里的变量running设置成传进来的参数id,将该id对应的协程状态status设置成COROUTINE_RUNNING,调用swapcontext保存当前协程上下文,激活执行参数id对应的协程。当协程为这个状态时,肯定是曾经被调度过了,即经历过了COROUTINE_READY阶段,其栈指针已经被修改过,因此不需要再次修改而直接激活执行。
不难看出,每个协程第一次被调度时,都调用了makecontext函数并把mainfunc函数设置成该协程执行时就去调用的函数,因此我们知道,协程co1和co2所对应函数foo和foo2都是在mainfunc中被调用。我们再看下foo和foo2的实现:
这两个函数中都有一个for循环,每循环一次就调用coroutine_yield函数,该函数首先将当前协程的状态status改为COROUTINE_SUSPEND,将调度器结构体里running变量设置为-1,再调用swapcontext将协程上下文保存在当前协程的结构体变量ctx中,激活调度器结构体里main变量对应的协程上下文,这里实际上是切换到了主协程。
说到这里,估计有些同学还是不明不白的,我根据自己的理解具体来解释一下流程:
while循环里边对协程co1调用coroutine_resume时,由于第一次调用进入COROUTINE_READY分支,这时候getcontext获取主协程(不知道描述对不对)的上下文,然后修改栈后作为协程上下文保存在co1对于的协程结构体中,然后mainfunc中执行co1对于的函数foo,在foo中调用了coroutine_yield,这时co1被设置成COROUTINE_SUSPEND,切换到刚才保存的主协程中,这是test函数里边的coroutine_resume又被调用,不过这时是对co2,同样的命运,co2对应的foo2被调度执行,没想到foo2函数也自动将自己设置成COROUTINE_SUSPEND,这时又切换到了主协程中,test中又一次循环开始,coroutine_resume对co1调用,只是这次进入COROUTINE_SUSPEND分支,这回不用设置什么栈了,直接换成执行协程co1,对co2也一样,不再赘述。
那么问题又来了,co1和co2什么时候彻底结束、主程序得以退出呢?
foo和foo2中的for循环次数是有限的,当循环条件不满足时,coroutine_yield函数不会被调用,这时mainfunc中的调用:“C->func(S,
C->arg); ”结束,之后的语句“C->status = COROUTINE_DEAD;”被调用,将对应的协程状态status设置为COROUTINE_DEAD。当两个协程状态都为COROUTINE_DEAD时,schedule_status函数返回0,主协程中的while循环退出,主程序就退出了。再看下程序的执行结果,一切都变得明了了。
运行结果:
使用ucontext组件实现的coroutine代码分析的更多相关文章
- JS日期级联组件代码分析及demo
最近研究下JS日期级联效果 感觉还不错,然后看了下kissy也正好有这么一个组件,也看了下源码,写的还不错,通过google最早是在2011年 淘宝的虎牙(花名)用原审JS写了一个(貌似据说是从YUI ...
- pmd静态代码分析
在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- JavaBean 基础概念、使用实例及代码分析
JavaBean 基础概念.使用实例及代码分析 JavaBean的概念 JavaBean是一种可重复使用的.且跨平台的软件组件. JavaBean可分为两种:一种是有用户界面的(有UI的):另一种是没 ...
- 免费的Lucene 原理与代码分析完整版下载
Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的 ...
- vue 2.0 路由切换以及组件缓存源代码重点难点分析
摘要 关于vue 2.0源代码分析,已经有不少文档分析功能代码段比如watcher,history,vnode等,但没有一个是分析重点难点的,没有一个是分析大命题的,比如执行router.push之后 ...
- 2018-2019-2 《网络对抗技术》Exp4 恶意代码分析 20165326
恶意代码分析 实践目标 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,systra ...
- 2018-2019 2 20165203 《网络对抗技术》 Exp4 恶意代码分析
2018-2019 2 20165203 <网络对抗技术> Exp4 恶意代码分析 实验要求 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp ...
- 20155202张旭 Exp4 恶意代码分析
20155202张旭 Exp4 恶意代码分析 实验前问题回答: 一:如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来 ...
随机推荐
- Linux暂停和恢复进程
Linux暂停和恢复进程 kill -STOP 1234 将该进程暂停. 如果要让它恢复到后台,用kill -CONT 1234 (很多在前台运行的程序这样是不行的) 如果要恢复到前台,请在当时运行该 ...
- 【Luogu】P1462通往奥格瑞玛的道路(二分答案+SPFA)
题目链接 导致我WA十几遍的原因居然是最大值不够大……以后再也不相信memset(dis,127/3,sizeof(dis))了. 此题先将花费排序,然后二分最大花费,spfa判断解是否可行.spfa ...
- HackerRank# Fibonacci Modified
原题地址 竟然64位都要爆,这是要大整数乘法的节奏吗?我才不要写大整数乘法呢,用Ruby干掉 代码: # Enter your code here. Read input from STDIN. Pr ...
- BZOJ 2190 [SDOI2008]仪仗队 ——Dirichlet积
[题目分析] 考虑斜率为0和斜率不存在的两条线上只能看到3人. 其余的人能被看见,当且仅当gcd(x,y)=1 ,然后拿卷积算一算 发现就是欧拉函数的前缀和的二倍. 注意2的情况要特判. [代码] # ...
- delphi使用IdHTTP模拟提交页面方法总结
http://blog.csdn.net/lxdcyh/article/details/3986800 1.拖入TIdHTTP控件,HandleRedirect设为True,否则可能会出现HTTP 3 ...
- Idea基本讲解、常用配置以及快捷键设置
IDEA官网学习文档:https://www.jetbrains.com/idea/documentation/ IDEA官网入门教程:https://www.jetbrains.com/help/i ...
- Js 流程控制
流程控制 顺序.分支.循环 顺序结构 代码一行一行从上往下执行并解析 分支结构 if语句 switch语句 if语句 单分支 if(条件表达式){ //语句块 } 含义:当条件表达式为真的时候就执行里 ...
- JavaSE的static、final、abstract修饰符
static :静态常量,静态方法,静态代码块 静态变量: 静态变量属于类的,使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问. 注意: ...
- Centos7 下安装 RabbitMQ
安装 erlang 1.下载erlang 官网地址 http://www.erlang.org/download 挑选合适的版本 然后 wget 比如目前最新版本 19.3 运行命令 wget htt ...
- iOS开发-用keychain替代UDID
从2013-5-1日开始苹果就禁止对UUID的应用的通过了.所以我们需要用一些办法替换,下面我就是用keychain的访问替换掉UUID的. 那么,关于Keychain的应用,Apple提供了一个叫G ...