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。没用过,所以也不深入考究了。

  • 相关阅读:
    线性dp
    Python3.6.5编译报错 configure: error: no acceptable C compiler found in $PATH
    hadoop伪分布式安装流程
    hadoop需要修改的配置文件
    Linux 免密登录和配置环境变量
    虚拟机修改主机名
    Netty自定义解码器
    Java 操作KafKa API
    Mysql分表:Merge
    CentOS7虚拟机配置ip地址
  • 原文地址:https://www.cnblogs.com/madao/p/3511532.html
Copyright © 2011-2022 走看看