Skip to content

Commit

Permalink
coroutine
Browse files Browse the repository at this point in the history
  • Loading branch information
crazytuzi committed Aug 11, 2020
1 parent c9609f4 commit 0f7c12e
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 19 deletions.
55 changes: 50 additions & 5 deletions lcorolib.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,37 @@
#include "lualib.h"


/*
** 协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用
** Lua没有独立的线程,所以每次执行脚本的时候,都是单线程执行
** 线程相对资源独立,有自己的上下文,由系统切换调用
** 协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制
** 协程的设计思路:
** Lua通过luaB_cocreate函数,去创建一个协程栈结构,这个协程栈结构是独立的数据栈结构,但是跟主线程栈共用了全局状态机global_State
** 当创建完新的协程栈,主线程上会将协程L和回调函数入栈(lua_pushvalue)
** 一个协程回调函数里面,可能还会嵌套和包含其他的协程,所以,协程是支持嵌套的
** 当我们调用luaB_coresume函数的时候,才会真正去执行协程的回调函数,该函数有两种状态需要处理:正常状态和yield挂起中断状态
** 正常状态处理:正常状态,最终会调用luaD_precall和luaV_execute,执行C闭包函数/内置API函数或者执行Lua字节码
** 中断状态处理:如果是中断状态,则在Lua的coroutine回调函数中,执行了luaB_yield方法,resume主要将中断的操作进行恢复
** 中断过程是通过抛出异常的方式完成的,当回调函数中调用luaB_yield中断处理的时候,会抛出一个LUA_YIELD的异常,
** resume函数是通过luaD_rawrunprotected异常保护方法去执行的,所以代码会跳到LUAI_TRY点,
** 然后根据L->status状态判断是中断还是正常状态,执行不同的代码逻辑
*/


static lua_State *getco (lua_State *L) {
lua_State *co = lua_tothread(L, 1);
luaL_argcheck(L, co, 1, "thread expected");
return co;
}


/*
** L - 原始线程栈
** co - 要启动的线程栈
** 如果返回值不是LUA_OK或LUA_YIELD,则表示错误,将co栈顶的错误对象转移到L栈顶
** 否则表示协程函数返回或中途有yield操作,将co栈中的返回值全部转移到L栈
*/
static int auxresume (lua_State *L, lua_State *co, int narg) {
int status;
if (!lua_checkstack(co, narg)) {
Expand All @@ -35,7 +59,7 @@ static int auxresume (lua_State *L, lua_State *co, int narg) {
lua_pushliteral(L, "cannot resume dead coroutine");
return -1; /* error flag */
}
lua_xmove(L, co, narg);
lua_xmove(L, co, narg); /* 将L上的栈数据拷贝到co上 */
status = lua_resume(co, L, narg);
if (status == LUA_OK || status == LUA_YIELD) {
int nres = lua_gettop(co);
Expand All @@ -54,8 +78,17 @@ static int auxresume (lua_State *L, lua_State *co, int narg) {
}


/*
** 当创建完一个协程后,就需要启动该协程
** 主要功能:启动 & 恢复 协程
** 首先调用getco方法从主线程栈上获取协程栈结构(如果多层嵌套就是上一层的栈)
** 然后调用auxresume方法,如果小于0表示出错,则栈顶是一个错误对象,此时在错误对象前面插入一个false
** 否则表示成功,栈顶是由协程函数或yield返回的参数,在这些参数之下插入一个true
** 线程相对资源独立,有自己的上下文,由系统切换调用
** 协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制
*/
static int luaB_coresume (lua_State *L) {
lua_State *co = getco(L);
lua_State *co = getco(L); /* 获取协程栈 */
int r;
r = auxresume(L, co, lua_gettop(L) - 1);
if (r < 0) {
Expand Down Expand Up @@ -86,12 +119,18 @@ static int luaB_auxwrap (lua_State *L) {
}


/*
** 协程创建函数,会创建一个独立的Lua栈结构
** 通过lua_newthread创建一个新的协程栈(lua_State),协程栈独立管理内部的栈资源
** 将CallInfo操作栈上的协程回调函数,移动到L->top数据栈顶部
** 拷贝回调函数到协程的数据栈上
*/
static int luaB_cocreate (lua_State *L) {
lua_State *NL;
luaL_checktype(L, 1, LUA_TFUNCTION);
luaL_checktype(L, 1, LUA_TFUNCTION); /* 创建一个新协程 */
NL = lua_newthread(L);
lua_pushvalue(L, 1); /* move function to top */
lua_xmove(L, NL, 1); /* move function from L to NL */
lua_pushvalue(L, 1); /* move function to top - 将CallInfo操作栈上的协程回调函数,移动到L->top数据栈顶部 */
lua_xmove(L, NL, 1); /* move function from L to NL - 拷贝回调函数到协程的数据栈上 */
return 1;
}

Expand All @@ -103,6 +142,12 @@ static int luaB_cowrap (lua_State *L) {
}


/*
** 协程挂起函数
** L - 当前的协程栈
** 为何参数L是当前协程栈呢?因为挂起函数,一般都是在协程回调函数内部使用,
** 回调函数是被resume函数执行的,执行环境为当前协程栈环境
*/
static int luaB_yield (lua_State *L) {
return lua_yield(L, lua_gettop(L));
}
Expand Down
43 changes: 30 additions & 13 deletions ldo.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,36 +689,50 @@ static int resume_error (lua_State *L, const char *msg, int narg) {
** inside a hook, or regularly suspended (optionally with a continuation
** function), plus erroneous cases: non-suspended coroutine or dead
** coroutine.
** 函数通过L->status去判断执行状态
** 当L->status = LUA_OK,则正常启动一个函数调用流程
** 当L->status = LUA_YIELD,则恢复中断的协程调用,并将状态设置为LUA_OK恢复调用
** 中断恢复,需要调用luaD_poscall进行yield函数执行的时候的堆栈调整,然后调用unroll,执行恢复动作
*/
static void resume (lua_State *L, void *ud) {
int n = *(cast(int*, ud)); /* number of arguments */
StkId firstArg = L->top - n; /* first argument */
CallInfo *ci = L->ci;
if (L->status == LUA_OK) { /* starting a coroutine? */
if (L->status == LUA_OK) { /* starting a coroutine? - 不为中断状态 */
if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */
luaV_execute(L); /* call it */
}
else { /* resuming from previous yield */
else { /* resuming from previous yield - 恢复中断挂起情况 */
lua_assert(L->status == LUA_YIELD);
L->status = LUA_OK; /* mark that it is running (again) */
ci->func = restorestack(L, ci->extra);
ci->func = restorestack(L, ci->extra); /* 调整协程栈的状态 */
if (isLua(ci)) /* yielded inside a hook? */
luaV_execute(L); /* just continue running Lua code */
else { /* 'common' yield */
luaV_execute(L); /* just continue running Lua code - 继续执行Lua代码 */
else { /* 'common' yield - 通用的中断部分处理 */
if (ci->u.c.k != NULL) { /* does it have a continuation function? */
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, ci, firstArg, n); /* finish 'luaD_precall' */
luaD_poscall(L, ci, firstArg, n); /* finish 'luaD_precall' - 调整堆栈 */
}
unroll(L, NULL); /* run continuation */
unroll(L, NULL); /* run continuation - 执行先前中断的协程 */
}
}


/*
** L - 当前启动栈
** from - 原始栈
** nargs - 参数个数
** lua_resume没有参数用于指出期望的结果数量,它总是返回被调用函数的所有结果
** 它没有用于指定错误处理函数的参数,发生错误时不会展开栈,这就可以在发生错误后检查栈中的情况
** 如果正在运行的函数让出(yield)了控制权,lua_resume就会返回一个特殊的代码LUA_YIELD,并将线程置于一个可以被再次恢复执行的状态
** 最重要的是,lua_resume通过异常保护方法luaD_rawrunprotected来调用执行的resume(中断挂起yield状态就是通过异常抛出来回到调用点的)
** L->nny = 0 设置允许挂起状态,协程栈上的操作,都会走luaD_call模式,而不会走luaD_callnoyield模式
*/
LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) {
int status;
unsigned short oldnny = L->nny; /* save "number of non-yieldable" calls */
Expand All @@ -735,7 +749,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) {
luai_userstateresume(L, nargs);
L->nny = 0; /* allow yields */
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
status = luaD_rawrunprotected(L, resume, &nargs);
status = luaD_rawrunprotected(L, resume, &nargs); /* 回调函数resume,入参L为线程栈 */
if (status == -1) /* error calling 'lua_resume'? */
status = LUA_ERRRUN;
else { /* continue running after recoverable errors */
Expand Down Expand Up @@ -763,28 +777,31 @@ LUA_API int lua_isyieldable (lua_State *L) {
}


/*
** 协程 - 方法中断操作
*/
LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx,
lua_KFunction k) {
CallInfo *ci = L->ci;
CallInfo *ci = L->ci; /* 获取操作栈 */
luai_userstateyield(L, nresults);
lua_lock(L);
api_checknelems(L, nresults);
if (L->nny > 0) {
if (L->nny > 0) { /* 如果L->nny > 0 的话,是不允许中断挂起 */
if (L != G(L)->mainthread)
luaG_runerror(L, "attempt to yield across a C-call boundary");
else
luaG_runerror(L, "attempt to yield from outside a coroutine");
}
L->status = LUA_YIELD;
ci->extra = savestack(L, ci->func); /* save current 'func' */
L->status = LUA_YIELD; /* 中间状态 */
ci->extra = savestack(L, ci->func); /* save current 'func' - 扩展字段上保存当前方法 */
if (isLua(ci)) { /* inside a hook? */
api_check(L, k == NULL, "hooks cannot continue after yielding");
}
else {
if ((ci->u.c.k = k) != NULL) /* is there a continuation? */
ci->u.c.ctx = ctx; /* save context */
ci->func = L->top - nresults - 1; /* protect stack below results */
luaD_throw(L, LUA_YIELD);
luaD_throw(L, LUA_YIELD); /* 抛出一个LUA_YIELD */
}
lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */
lua_unlock(L);
Expand Down
7 changes: 6 additions & 1 deletion lstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ static void close_state (lua_State *L) {
}


/*
** 创建一个新的线程栈
** Lua在main函数中,调用luaL_newstate方法,创建了主线程(即lua_State* L)
** 主要用于实现Lua的协程实现(Lua没有多线程实现)
*/
LUA_API lua_State *lua_newthread (lua_State *L) {
global_State *g = G(L);
lua_State *L1;
Expand All @@ -279,7 +284,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
L1->next = g->allgc;
g->allgc = obj2gco(L1);
/* anchor it on L stack */
setthvalue(L, L->top, L1);
setthvalue(L, L->top, L1); /* 在栈顶上设置一个新的L1对象 */
api_incr_top(L);
preinit_thread(L1, g);
L1->hookmask = L->hookmask;
Expand Down

0 comments on commit 0f7c12e

Please sign in to comment.