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