zoukankan      html  css  js  c++  java
  • lua协程实现简析

    协程,简单来说就是新创建一个协助程序(co = coroutine.create(func)),然后需要手动去启动它(coroutine.resume(co)),在它最终退出之前,它有可能暂停多次返回阶段性的结果(coroutine.yield(co)),每一次暂停之后都必须手动去恢复它(coroutine.resume(co))。

    协程在lua源文件中对应lcorolib.c,数组co_funcs中定义了c暴露给lua的接口。从上面的描述看和c函数调用有点相似,只不过c函数只有一个出口,所以不可能返回多次。题外话,为什么c函数只有一个出口?我自己粗浅的理解是因为c函数的所有信息都放在栈上,而c语言没有提供原生的保存/恢复栈空间的支持,所以没有中途退出后还能生新进入这个概念。实际上,协程和系统级别的进程切换更像一点,都是保存堆栈,然后恢复。我想最大的不同就是协程知道接下来的控制权在哪里,而进程不知道。根本上它们想实现的功能就不一样吧。

    好了,那协程实现的要点就是堆栈的保存与恢复了。当然,这里的堆栈不是进程本身的堆栈,而是lua的soft stack。从代码上来说吧:

     82 static int luaB_cocreate (lua_State *L) {
     83   lua_State *NL;
     84   luaL_checktype(L, 1, LUA_TFUNCTION);
     85   NL = lua_newthread(L);
     86   lua_pushvalue(L, 1);  /* move function to top */
     87   lua_xmove(L, NL, 1);  /* move function from L to NL */
     88   return 1;
     89 }

    其中NL就是新创建的协程的栈,以后所有的保存/恢复都是针对这个栈。lua_State这个结构体里对协程实现最重要的是CallInfo *ci,CallInfo的定义如下:

     66 /*
     67 ** information about a call
     68 */
     69 typedef struct CallInfo {
     70   StkId func;  /* function index in the stack */
     71   StkId top;  /* top for this function */
     72   struct CallInfo *previous, *next;  /* dynamic call link */
     73   short nresults;  /* expected number of results from this function */
     74   lu_byte callstatus;
     75   ptrdiff_t extra;
     76   union {
     77     struct {  /* only for Lua functions */
     78       StkId base;  /* base for this function */
     79       const Instruction *savedpc;
     80     } l;
     81     struct {  /* only for C functions */
     82       int ctx;  /* context info. in case of yields */
     83       lua_CFunction k;  /* continuation in case of yields */
     84       ptrdiff_t old_errfunc;
     85       lu_byte old_allowhook;
     86       lu_byte status;
     87     } c;
     88   } u;
     89 } CallInfo;

    其中func指向当前调用的函数在栈上的位置,而savedpc就是保存的指令执行位置(先无视union里的c),根据这两个值就能恢复函数的执行点。然而在yield的时候真正负责保存函数位置的是extra(保存func与栈顶的相对位置),在resume时func会根据extra来恢复,有没有这个需要我是表示怀疑的,因为就算resume传递的参数导致栈realloc,使func失效,但在luaD_reallocstack内会调用correctstack将调用链上所有的func重新设置为正确的值,所以这里是不是多余的呢?

    在lua 5.2中调用路径包含c函数的时候也能够进行yield,只不过不甚好看。由于c函数不能保存堆栈,所以lua的策略是直接放弃当前c函数的栈幀,而让调用者本身提供一个continuation,当resume时调用上面被无视的uion里的c.k。没用过,所以也不深入考究了。

  • 相关阅读:
    WHERE col1=val1 AND col2=val2;index exists on col1 and col2, the appropriate rows can be fetched directly
    MySQL 交集 实现方法
    MBProgressHUD的使用
    Xcode4 使用 Organizer 分析 Crash logs(转)
    SimpleXML 使用详细例子
    PHP的XML Parser(转)
    iPhone,iPhone4,iPad程序启动画面的总结 (转)
    Pop3得到的Email 信件格式介绍
    yii总结
    隐藏Tabbar的一些方法
  • 原文地址:https://www.cnblogs.com/madao/p/3511532.html
Copyright © 2011-2022 走看看