烦恼一般都是想太多了。

0%

再看Lua中的协程实现

Lua中的协程 中确实已经介绍过了如何使用协程库,但是没有具体了解其实现,稍微麻烦一点就感觉有点茫然,所以需要来看一下其具体是如何实现的。

在文章 lua中的C-API及代码流程 也介绍了 Lua 虚拟机的一些概念,如线程与虚拟机等。

基本概念

一般来说,在 5.3 我们都会调用 luaL_newstate() 来建立一个新的虚拟机 lua_State。而这个本质实际上就是调用 lua_newstate(),止不过使用了默认的一个内存分配函数而已。

实际上在这个函数中,他主要分配了几个数据结构:

  • global_State 全局虚拟机环境。
  • lua_State 每线程相关的虚拟机环境(注意,这个要和系统线程相区分,这个是Lua内所称呼的线程),看代码内的定义,lua_State 的 tt 字段被设置为 LUA_THREAD
  • LX lua_State 加上额外的字节数组空间。
  • LG LX加上一个全局环境,这个也被程为虚拟机中的主线程

其中 lua_State 会有 global_State 的引用,这样就可以通过 G(L) 来获取任意一个 lua_State 的全局环境。

因此,对 Lua 来说,每个线程,即是一个 lua_State,表示可执行代码序列。

因为 Lua 的每个 C API 都需要指定一个 lua_State ,也可以叫做线程信息,来执行代码,所以完全可以在不同的线程中来执行代码。

coroutine

想象一下我们的工作场景,一般来说我们的 Lua 开始肯定是在主线程中的。然而,我们会利用 coroutine.create(function end) 来建立一个协程,那么这背后是做了什么呢?

create

// lcorolib.c
static int luaB_cocreate (lua_State *L) {
lua_State *NL;
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 */
return 1;
}

很简单,建立了一个新线程(lua_State),然后将传递进来的函数,移到新的线程内。

同时,这个函数的返回值,其实就是我们新建的线程。

接着,我们就需要让协程进行运行。

resume

// lcorolib.c
static int luaB_coresume (lua_State *L) {
lua_State *co = getco(L);
int r;
r = auxresume(L, co, lua_gettop(L) - 1);
if (r < 0) {
lua_pushboolean(L, 0);
lua_insert(L, -2);
return 2; /* return false + error message */
}
else {
lua_pushboolean(L, 1);
lua_insert(L, -(r + 1));
return r + 1; /* return true + 'resume' returns */
}
}

static int auxresume (lua_State *L, lua_State *co, int narg) {
int status;
if (!lua_checkstack(co, narg)) {
lua_pushliteral(L, "too many arguments to resume");
return -1; /* error flag */
}
if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) {
lua_pushliteral(L, "cannot resume dead coroutine");
return -1; /* error flag */
}
lua_xmove(L, co, narg);
status = lua_resume(co, L, narg);
if (status == LUA_OK || status == LUA_YIELD) {
int nres = lua_gettop(co);
if (!lua_checkstack(L, nres + 1)) {
lua_pop(co, nres); /* remove results anyway */
lua_pushliteral(L, "too many results to resume");
return -1; /* error flag */
}
lua_xmove(co, L, nres); /* move yielded values */
return nres;
}
else {
lua_xmove(co, L, 1); /* move error message */
return -1; /* error flag */
}
}

我们看到, resume 的时候会还可以传递参数,但是第一个肯定是 协程本身。通过将参数移动到协程中的 lua_State 内去,然后恢复其进行执行。

还可以看到,当 协程返回的时候,在协程栈内的值都会被移动到调用协程的线程内去。

如此,在我们调用 yield 的时候,如果传递了参数,这些参数会首先压到协程的栈上,然后在 resume 返回的时候,显示给调用线程。

总而言之两句话:

  • 我们可以在 coroutine.resume 的时候,将参数放在一个协程的栈上由其协程进行使用。
  • 也可以在 coroutine.yield 的时候,将想要返回的值放在协程的栈上,交给调用线程
  • 如果一个协程 yield 后,再次 resume 的时候带了参数,那么这些参数将会是 yield 的返回值。

这就是说,对于 coroutine 库,往协程的 lua_State 上放参数,可以用来进行在协程和调用线程间进行通信。