lua协程

    lua的协程接口简单、功能强大能方便地处理一些非常规的功能需求。

    1、创建协程接口:coroutine.create(f)

          参数f是一个函数,创建成功会返回一个协程对象,该协程处于挂起状态。

     2、运行协程接口:coroutine.resume(co,...)

          co是处于挂起状态的协程。

          第一次运行该协程时,指令会从函数的第一各指令开始执行

          后面再次运行协程时,会从之前调用coroutine.yield()指令的地方返回,然后开始运行。

     3、代码示例

          协程函数

          function fun(a)

               a = a + 1

               b = coroutine.yield(a)

               b = b + 1

               return b

          end

          local  co = coroutine.create(f)

          local a = 1 

          a = coroutine.resume(a)-- 将a作为参数resume协程,协程co调用yield阻塞自己,并将a+1的值返回,a的值变成了2

          local b = 1

          b = coroutine.resume(b)     -- 将b作为参数resume协程,co协程会从上次的yield阻塞中恢复运行,得到了resume传入的b

        4、协程的几个特点:

               A、协程不能被动中断,只能自己让出运行权

               B、协程的中断和继续运行都能传递参数

               C、协程中断时能保存调用栈,下次调用resume后,恢复之前的运行状态。但是如果栈中有C函数,C函数的运行环境(运行到哪一行代码,栈上各变量的值,寄存器的值)因为longjump已在yield时被销毁。在下次resume时,返回到这一层调用时发现这是一个C函数,但是不知道该怎样处理(是重新执行一遍?还是直接返回上层调用?),显然都不对。在lua 5.2以后提供了一个参数,该参数是一个C函数K,当返回到之前中断的C函数这一层调用时,直接运行K。

           直接上代码:在resume中,会直接调用unroll来继续运行之前阻塞的协程调用栈。如果是LUA函数,直接执行,如果是C函数,则调用finishCcall取代原来在调用栈上的C函数,来进行处理。

/*
** Completes the execution of an interrupted C function, calling its
** continuation function.
*/
static void finishCcall (lua_State *L, int status) {
  CallInfo *ci = L->ci;
  int n;
  /* must have a continuation and must be able to call it */
  lua_assert(ci->u.c.k != NULL && L->nny == 0);
  /* error status can only happen in a protected call */
  lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD);
  if (ci->callstatus & CIST_YPCALL) {  /* was inside a pcall? */
    ci->callstatus &= ~CIST_YPCALL;  /* finish 'lua_pcall' */
    L->errfunc = ci->u.c.old_errfunc;
  }
  /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already     handled */
     handled */
     handled */
  adjustresults(L, ci->nresults);
  /* call continuation function */
  lua_unlock(L);
  n = (*ci->u.c.k)(L, status, ci->u.c.ctx);
  lua_lock(L);
  api_checknelems(L, n);
  /* finish 'luaD_precall' */
  luaD_poscall(L, L->top - n);
}
/*
** Executes "full continuation" (everything in the stack) of a
** previously interrupted coroutine until the stack is empty (or another
** interruption long-jumps out of the loop). If the coroutine is
** recovering from an error, 'ud' points to the error status, which must
** be passed to the first continuation function (otherwise the default
** status is LUA_YIELD).
*/
static void unroll (lua_State *L, void *ud) {
if (ud != NULL) /* error status? */
finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */
while (L->ci != &L->base_ci) { /* something in the stack */
if (!isLua(L->ci)) /* C function? */
finishCcall(L, LUA_YIELD); /* complete its execution */
else { /* Lua function */
luaV_finishOp(L); /* finish interrupted instruction */
luaV_execute(L); /* execute down to higher C 'boundary' */
}
}
}
/*
** Do the work for 'lua_resume' in protected mode. Most of the work
** depends on the status of the coroutine: initial state, suspended
** inside a hook, or regularly suspended (optionally with a continuation
** function), plus erroneous cases: non-suspended coroutine or dead
** coroutine.
*/
static void resume (lua_State *L, void *ud) {
  int nCcalls = L->nCcalls;
  StkId firstArg = cast(StkId, ud);
  CallInfo *ci = L->ci;
  if (nCcalls >= LUAI_MAXCCALLS)
    resume_error(L, "C stack overflow", firstArg);
  if (L->status == LUA_OK) {  /* may be starting a coroutine */
    if (ci != &L->base_ci)  /* not in base level? */
      resume_error(L, "cannot resume non-suspended coroutine", firstArg);
    /* coroutine is in base level; start running it */
    if (!luaD_precall(L, firstArg - 1, LUA_MULTRET))  /* Lua function? */
      luaV_execute(L);  /* call it */
  }
  else if (L->status != LUA_YIELD)
    resume_error(L, "cannot resume dead coroutine", firstArg);
  else {  /* resuming from previous yield */
    L->status = LUA_OK;  /* mark that it is running (again) */
    ci->func = restorestack(L, ci->extra);
    if (isLua(ci))  /* yielded inside a hook? */
      luaV_execute(L);  /* just continue running Lua code */
    else {  /* 'common' yield */
      if (ci->u.c.k != NULL) {  /* does it have a continuation function? */
        int n;
        lua_unlock(L);
        n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx);/* call continuation */
        lua_lock(L);
        api_checknelems(L, n);
        firstArg = L->top - n;  /* yield results come from continuation */
      }
      luaD_poscall(L, firstArg);  /* finish 'luaD_precall' */
    }
    unroll(L, NULL);  /* run continuation */
  }
  lua_assert(nCcalls == L->nCcalls);
}

5、C函数yield调用异常示例

在lmath.c中的math库中加入函数add,add调用传入的lua函数将另两个参数相加,然后返回。

 static int math_add(lua_State *L) {
     lua_Number a = luaL_checknumber(L, 1);
     lua_Number b = luaL_checknumber(L, 2);
     // 栈索引为3的地方是传入的lua函数
     // 压入参数
     lua_pushinteger(L, a);

     lua_pushinteger(L, b);

     lua_call(L, 2, 1);

     return 1;

 }

lua代码如下:

function add(a, b)
coroutine.yield(0)
return a + b
end

function f()

        -- 将lua函数add传给math.add进行处理
local ret = math.add(1,2,add)
print("1 + 2 =",ret)
end

local co = coroutine.create(f)
print("调用resume")
coroutine.resume(co)
print("再次调用resume")     
coroutine.resume(co)

由于math.add是C函数,导致在调用lua_yieldk时,判断L->nny > 0,抛出了运行时异常。

 luaG_runerror(L, "attempt to yield across a C-call boundary");

co协程处于错误状态,无法再调用coroutine.resume

6、能正确处理yield的C函数写法:

// 加入一个resume后调用的函数

static int resumef(lua_State *L, int d1, lua_KContext d2) {
  (void)d1;  (void)d2;  /* only to match 'lua_Kfunction' prototype */
  return 1;
}

 static int math_add(lua_State *L) {
     lua_Number a = luaL_checknumber(L, 1);
     lua_Number b = luaL_checknumber(L, 2);
     // 栈索引为3的地方是传入的lua函数
     // 压入参数
     lua_pushinteger(L, a);

     lua_pushinteger(L, b);

     // 调用lua_callk时,传入一个恢复函数,当下次resume时,该函数将被调用。

     lua_callk(L, 2, 1, 0, resumef);

     return 1;

 }

运行结果:

调用resume                                             
再次调用resume                                      
从阻塞中恢复过来                                   
1 + 2 = 3                                              





猜你喜欢

转载自blog.csdn.net/yuanfengyun/article/details/53470692