查看: 1917|回复: 0

[转贴]协程的C实现 by A WING BY WIND

[复制链接]
  • TA的每日心情
    郁闷
    6 小时前
  • 签到天数: 1625 天

    连续签到: 3 天

    [LV.Master]伴坛终老

    发表于 2013-3-18 09:21:48 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 nemon 于 2013-3-18 09:24 编辑

    from:http://www.hawkwithwind.net/blog/2011/02/18/%E5%8D%8F%E7%A8%8B%E7%9A%84c%E5%AE%9E%E7%8E%B0/
    auth:A WING BY WIND

    今天正好跟ownwaterloo聊到协程,于是查了查资料,顺便写个博客记录一下吧。
    我主要参考的是这篇资料http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html,是Simon Tatham提出的一个协程的C实现,非常有意思。
    协程的思想主要是为了解决多个任务如何分享CPU这个问题的。线程在很多协作问题上处理的不好,而且需要锁机制,导致运行缓慢,不易理解,容易出错。协程的思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制(或者说他本身就是一个全局锁)。
    Simon的举例是一个生产者消费者例子。传统的程序可能是生产者一个循环不断产生字符,之后退出。而消费者一个循环不断读取字符,并处理之。使用时或许会用一个管道,将生产者的输出重定向到消费者的输入。从而使两者协作。
    Simon提出,如何使这样的简单任务无需通过管道这类操作系统相关的重型机制,就能完成。他提出,生产者或者消费者之间,有一方需要改变自己的工作模式。不再是在循环中不断处理任务,而是一次只处理一个字符然后立刻返回。而另一方依旧保持原状,只需在原本输出到或读取自标准IO的地方修改为调用对方的那个函数就可以了。
    但这种写法难以维护。他提出了协程的概念,并期待写出类似这样的代码:
    1. int function(void) {
    2.     int i;
    3.     for (i = 0; i < 10; i++)
    4.         return i;   /* won't work, but wouldn't it be nice */
    5. }
    复制代码
    在这里return语句并不是终止函数,而是将函数转入睡眠的状态,并将CPU交付给另一个函数执行。例如生产者写入一个字符后,调用类似的return语句,交付消费者处理这个字符。稍后当消费者处理完并调用return语句后,能重新激活生产者协程,并继续这个for循环。
    如何在C语言中实现这样的功能而不需使用汇编代码去hack堆栈和寄存器呢?他给出的最初的实现是使用goto语句。
    1. int function(void) {
    2.     static int i, state = 0;
    3.     switch (state) {
    4.         case 0: goto LABEL0;
    5.         case 1: goto LABEL1;
    6.     }
    7.     LABEL0: /* start of function */
    8.     for (i = 0; i < 10; i++) {
    9.         state = 1; /* so we will come back to LABEL1 */
    10.         return i;
    11.         LABEL1:; /* resume control straight after the return */
    12.     }
    13. }
    复制代码
    巧妙的使用静态变量存储函数状态,并使用goto语句来继续for循环。但这种写法不够优美,于是又引入了Duff’s device。
    1. switch (count % 8) {
    2.         case 0:        do {  *to = *from++;
    3.         case 7:              *to = *from++;
    4.         case 6:              *to = *from++;
    5.         case 5:              *to = *from++;
    6.         case 4:              *to = *from++;
    7.         case 3:              *to = *from++;
    8.         case 2:              *to = *from++;
    9.         case 1:              *to = *from++;
    10.                        } while ((count -= 8) > 0);
    11.     }
    复制代码
    使用这种trick来将switch-case语句作为循环中的跳转。这就避免了goto-label语句需要同时维护goto和label两处代码的麻烦。于是前面的代码可以变成这个样子。
    1. int function(void) {
    2.     static int i, state = 0;
    3.     switch (state) {
    4.         case 0: /* start of function */
    5.         for (i = 0; i < 10; i++) {
    6.             state = 1; /* so we will come back to "case 1" */
    7.             return i;
    8.             case 1:; /* resume control straight after the return */
    9.         }
    10.     }
    11. }
    复制代码
    他的文章后面还有一些内容,例如如何使用宏包装这套机制。如何使用__LINE__宏避免state被赋予相同的数值。如何避免多线程调用下干扰静态变量等等。我这里就不赘述了,大家有兴趣可参考原文。
    总之读此文的两个收获一个是认识协程,一个是学习到了一种诡谲的C语言用法。非常开心。

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-26 13:35 , Processed in 0.115717 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.