使用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 恶意代码分析 实验前问题回答: 一:如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来 ...
随机推荐
- hadoop学习爬坑记录
1. Q: hdfs管理界面50070端口设置后,无法访问情况. A: 1)停止当前所有服务./stop-all.sh 2)在hdfs-site.xml中,更改开放端口的绑定IP: <prope ...
- iOS8 WebKit库之——WKWebView篇
iOS8 WebKit库之--WKWebView篇 webkit使用WKWebView来代替IOS的UIWebView和OSX的WebView,并且使用Nitro JavaScript引擎,这意味着所 ...
- git删除本地所有的更改
删除本地所有为暂存的修改: git checkout -f 如果有修改以及加入暂存区的话 那么 使用如下命令: git reset --hard git clean -xdf
- python类可以截获Python运算符
类可以截获Python运算符 现在,让我们来看类和模块的第三个主要差别: 运算符重载.简而言之,运算符重载就是让用类写成的对象,可截获并响应用在内置类型上的运算:加法.切片.打印和点号运算等.这只是自 ...
- BZOJ1926 [Sdoi2010]粟粟的书架 【主席树 + 二分 + 前缀和】
题目 幸福幼儿园 B29 班的粟粟是一个聪明机灵.乖巧可爱的小朋友,她的爱好是画画和读书,尤其喜欢 Thomas H. Co rmen 的文章.粟粟家中有一个 R行C 列的巨型书架,书架的每一个位置都 ...
- java面试题之Thread的run()和start()方法有什么区别
run()方法: 是在主线程中执行方法,和调用普通方法一样:(按顺序执行,同步执行) start()方法: 是创建了新的线程,在新的线程中执行:(异步执行) public class App { pu ...
- POJ 3468 线段树 成段更新 懒惰标记
A Simple Problem with Integers Time Limit:5000MS Memory Limit:131072K Case Time Limit:2000MS Descr ...
- Python入门--13--爬虫一
URL的格式一般为(带方括号的是可选的): protocol://hostname[:port]/path/[;parameters][?query]#fragment URL由三部分组成: 第一部分 ...
- ci框架——修改分页的显示样式
修改ci框架分页的显示样式 用过ci框架的都知道,ci框架自带的分页样式是1,2下一页,在最开始刷新页面现实的时候如果页面不够多的话,那么首页和末页是不显的,这是ci框架的一个缺点, 这个时候需要我们 ...
- 487. Max Consecutive Ones II
Given a binary array, find the maximum number of consecutive 1s in this array if you can flip at mos ...