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代码分析的更多相关文章

  1. JS日期级联组件代码分析及demo

    最近研究下JS日期级联效果 感觉还不错,然后看了下kissy也正好有这么一个组件,也看了下源码,写的还不错,通过google最早是在2011年 淘宝的虎牙(花名)用原审JS写了一个(貌似据说是从YUI ...

  2. pmd静态代码分析

    在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...

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

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

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

    JavaBean 基础概念.使用实例及代码分析 JavaBean的概念 JavaBean是一种可重复使用的.且跨平台的软件组件. JavaBean可分为两种:一种是有用户界面的(有UI的):另一种是没 ...

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

    Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的 ...

  6. vue 2.0 路由切换以及组件缓存源代码重点难点分析

    摘要 关于vue 2.0源代码分析,已经有不少文档分析功能代码段比如watcher,history,vnode等,但没有一个是分析重点难点的,没有一个是分析大命题的,比如执行router.push之后 ...

  7. 2018-2019-2 《网络对抗技术》Exp4 恶意代码分析 20165326

    恶意代码分析 实践目标 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,systra ...

  8. 2018-2019 2 20165203 《网络对抗技术》 Exp4 恶意代码分析

    2018-2019 2 20165203 <网络对抗技术> Exp4 恶意代码分析 实验要求 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp ...

  9. 20155202张旭 Exp4 恶意代码分析

    20155202张旭 Exp4 恶意代码分析 实验前问题回答: 一:如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来 ...

随机推荐

  1. Java-对复合类型数据进行排序

    Array.sort(arr)可以进行简单的排序,如果需要复杂的排序可以实现Comparable package com.tj; import java.util.Arrays; public cla ...

  2. Leetcode 407.接雨水

    接雨水 给定一个 m x n 的矩阵,其中的值均为正整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水. 说明: m 和 n 都是小于110的整数.每一个单位的高度都大于0 且小 ...

  3. 快速排序php

    <?php /** * Created by PhpStorm. * User: brady.wang * Date: 2017/11/10 * Time: 9:45 */ $arr=array ...

  4. vue 使用Echarts 环形图 自定义legend formatter 富文本标签

    main.js 引入echarts // 引入echarts import Echarts from 'echarts' Vue.prototype.$echarts = Echarts   < ...

  5. ios弹性头部

    很久没写博客了,金天有点时间来写下,一直觉得弹性头部很炫,看起来高大上,写起来蛮简单的 层次分析 一共有3层,最底部是图像层,中间是scrollView或者它的子类,最上层是scrollView上面添 ...

  6. BZOJ 1007 [HNOI2008]水平可见直线 ——半平面交 凸包

    发现需要求一个下凸的半平面上有几个交点. 然后我们把它变成凸包的问题. 好写.好调.还没有精度误差. #include <map> #include <ctime> #incl ...

  7. UOJ 274 【清华集训2016】温暖会指引我们前行 ——Link-Cut Tree

    魔法森林高清重置, 只需要维护关于t的最大生成树,然后链上边权求和即可. 直接上LCT 调了将近2h 吃枣药丸 #include <cstdio> #include <cstring ...

  8. sublime text3安装angularjs插件

    sublime能够支持AngularJS开发那绝对是一件很爽的事情.下面我一步步讲解如何为sublime安装AngularJS插件. 首先提供一个破解版的sublime text 3的下载地址:htt ...

  9. Scrapy学习-3-Request回调巧用

    基于twisted的异步回调 使得页面爬取有阶段性和连续性 from scrapy.http import Request from urllib import parse def parse(sel ...

  10. Lighttpd 服务器的安装

    https://www.cnblogs.com/rongfengliang/articles/3503228.html