使用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循环退出,主程序就退出了。再看下程序的执行结果,一切都变得明了了。

运行结果:

时间: 2024-11-10 08:19:33

使用ucontext组件实现的coroutine代码分析的相关文章

characterCustomezition的资源打包代码分析

using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; class CreateAssetbundles { // This method creates an assetbundle of each SkinnedMeshRenderer // found in any selected character fbx, and adds any materials that

完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化的方式进行配置,所以维护起来相当困难.Gradle:Gradle采用增量构建.Gradle通过Groovy编程而不是传统的XML声明进行配置.Gradle可以很好地配合Maven进行依赖管理,并且把Ant脚本当作头等公民.字节码操作 编程操作Java字节码的函数库. ASM:通用底层字节码操作及分析

恶意代码分析实战

恶意代码分析实战(最权威的恶意代码分析指南,理论实践分析并重,业内人手一册的宝典) [美]Michael Sikorski(迈克尔.斯科尔斯基), Andrew Honig(安德鲁.哈尼克)著   <恶意代码分析实战>是一本内容全面的恶意代码分析技术指南,其内容兼顾理论,重在实践,从不同方面为读者讲解恶意代码分析的实用技术方法. <恶意代码分析实战>分为21章,覆盖恶意代码行为.恶意代码静态分析方法.恶意代码动态分析方法.恶意代码对抗与反对抗方法等,并包含了 shellcode分析

pmd静态代码分析

在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可能的bug--空的try/catch/finally/switch块.– 无用代码(Dead code):无用的本地变量,方法参数和私有方法.– 空的if/while语句.– 过度复杂的表达式--不必要的if语句,本来可以用while循环但是却用了for循环.– 可优化的代码:浪费性能的String

JavaBean 基础概念、使用实例及代码分析

JavaBean 基础概念.使用实例及代码分析 JavaBean的概念 JavaBean是一种可重复使用的.且跨平台的软件组件. JavaBean可分为两种:一种是有用户界面的(有UI的):另一种是没有用户界面的(无UI的),无UI的JavaBean主要负责处理事务(如数据运算,操纵数据库). JSP通常访问的是后一种JavaBean. JSP与JavaBean搭配使用的优点 使得HTML与Java程序分离,这样便于维护代码. 如果把所有的程序代码都写到JSP网页中,会使得代码繁杂,难以维护.

贪吃蛇小游戏java实现代码分析

贪吃蛇小游戏java实现代码分析 贪吃蛇的小游戏,网上的代码比较多,今天周五,在教研室没啥事做,在电脑中发现了一个贪吃蛇的小游戏,于是就看了下实现的源码,发现别人写的代码确实挺好的,自己也是边加注释边进行理解的去看别人实现的游戏源码,发现还是挺有意思的.自己花了一个下午的时间看了源码,也加了一点小小的功能,于是,不写篇博客觉得对不起自己也,哈哈哈. 此游戏代码的思路非常的清晰,也相当好理解,没有太多难的地方,不过有很多值得学习的地方,因为,这份源码中,对java.awt包中的很多类的很多方法都进

免费的Lucene 原理与代码分析完整版下载

Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的或者有限长度的数据,比如数据库,元数据等.非结构化数据则是不定长或者没有固定格式的数据,如图片,邮件,文档等.还有一种较少的分类为半结构化数据,如XML,HTML等,在一定程度上我们可以将其按照结构化数据来处理,也可以抽取纯文本按照非结构化数据来处理.非结构化数据又称为全文数据.,对其搜索主要有两种

2017-2018-2 《网络对抗技术》 20155322 Exp4 恶意代码分析

[-= 博客目录 =-] 1-实践目标 1.1-实践介绍 1.2-实践内容 1.3-实践要求 2-实践过程 2.1-Mac下网络监控 2.2-Windows下网络监控 2.3-Mac下恶意软件分析 2.4-Windows下恶意软件分析 2.5-基础问题回答 3-资料 1-实践目标 1.1-恶意代码分析 一般是对恶意软件做处理,让它不被杀毒软件所检测.也是渗透测试中需要使用到的技术. 要做好免杀,就时清楚杀毒软件(恶意软件检测工具)是如何工作的.AV(Anti-virus)是很大一个产业.其中主要

一个较丰满的servlet web server,包含conector、Processor、bootstrap (2代码分析)

代码分析: 类关系: BootStrap主程序负责服务器的启动,控制HttpConnector组件: HttpConnector类负责Http链接和线程管理,控制HttpProcessor组件: HttpProcessor类负责Http协议的解析和res/req的创建,同包下的其他类是为解析req解耦出来的相关类,为获取响应writer的相关类:request对象属性在本应用中的到 近乎完全的解析,response好像只可以用writer由程序员自行设置响应头?书上没做说明,自己看代码也无法从静