经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Lua » 查看文章
lua协程实现
来源:cnblogs  作者:bytemode  时间:2018/12/17 9:42:51  对本文有异议

协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制;另外,协程也更加轻量级。这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU;而当条件满足时,可以继续执行这个协程。目前在网络服务器领域,使用Lua协程最好的范例就是ngx_lua了

来看看Lua协程内部是如何实现的。

本质上,每个Lua协程其实也是对应一个LuaState指针,所以其实它内部也是一个完整的Lua虚拟机—有完整的Lua堆栈结构,函数调用栈等等等等,绝大部分之前对Lua虚拟机的分析都可以直接套用到Lua协程中。于是,由Lua虚拟机管理着这些隶属于它的协程,当需要暂停当前运行协程的时候,就保存它的运行环境,切换到别的协程继续执行。很简单的实现。

来看看相关的API。

  1. lua_newthread

创建一个Lua协程,最终会调用的API是luaE_newthread,Lua协程在Lua中也是一个独立的Lua类型数据,它的类型是LUA_TTHREAD,创建完毕之后会照例初始化Lua的栈等结构,有一点需要注意的是,调用preinit_state初始化Lua协程的时候,传入的global表指针是来自于Lua虚拟机,换句话说,任何在Lua协程修改的全局变量,也会影响到其他的Lua协程包括Lua虚拟机本身。

  1. 加载一个Lua文件并且执行

对于一般的Lua虚拟机,大可以直接调用luaL_dofile即可,它其实是一个宏:

  1. #define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

展开来也就是当调用luaL_loadfile函数完成对该Lua文件的解析,并且没有错误时,调用lua_pcall函数执行这个Lua脚本。

但是对于Lua协程而言,却不能这么做,需要调用luaL_loadfile然后再调用lua_resume函数。所以两者的区别在于lua_pcall函数和lua_resume函数。来看看lua_resume函数的实现。这个函数做的几件事情:首先查看当前Lua协程的状态对不对,然后修改计数器:

  1. L->baseCcalls = ++L->nCcalls;

其次调用status = luaD_rawrunprotected(L, resume, L->top – nargs);,可以看到这个保护Lua函数堆栈的调用luaD_rawrunprotected最终调用了函数resume:

  1. static void resume (lua_State *L, void *ud) {
  2. StkId firstArg = cast(StkId, ud);
  3. CallInfo *ci = L->ci;
  4. if (L->status == 0) { /* start coroutine? */
  5. lua_assert(ci == L->base_ci && firstArg > L->base);
  6. if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
  7. return;
  8. }
  9. else { /* resuming from previous yield */
  10. lua_assert(L->status == LUA_YIELD);
  11. L->status = 0;
  12. if (!f_isLua(ci)) { /* `common' yield? */
  13. /* finish interrupted execution of `OP_CALL' */
  14. lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
  15. GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
  16. if (luaD_poscall(L, firstArg)) /* complete it... */
  17. L->top = L->ci->top; /* and correct top if not multiple results */
  18. }
  19. else /* yielded inside a hook: just continue its execution */
  20. L->base = L->ci->base;
  21. }
  22. luaV_execute(L, cast_int(L->ci - L->base_ci));
  23. }

这个函数将执行Lua代码的流程划分成了几个阶段,如果调用

  1. luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA

那么说明这次调用返回的结果小于0,可以跟进luaD_precall函数看看什么情况下会出现这样的情况:

  1. n = (*curr_func(L)->c.f)(L); /* do the actual call */
  2. lua_lock(L);
  3. if (n < 0) /* yielding? */
  4. return PCRYIELD;
  5. else {
  6. luaD_poscall(L, L->top - n);
  7. return PCRC;
  8. }

继续回到resume函数中,如果之前该Lua协程的状态是YIELD,那么说明之前被中断了,则调用luaD_poscall完成这个函数的调用。
然后紧跟着调用luaV_execute继续Lua虚拟机的继续执行。

可以看到,resume函数做的事情其实有那么几件:

  1. 如果调用C函数时被YIELD了,则直接返回
  2. 如果之前被YIELD了,则调用luaD_poscall完成这个函数的执行,接着调用luaV_execute继续Lua虚拟机的执行。

因此,这个函数对于函数执行中可能出现的YIELD,有充分的准备和判断,因此它不像一般的pcall那样,一股脑的往下执行,而是会在出现YIELD的时候保存现场返回,在继续执行的时候恢复现场。
3)同时,由于resume函数是由luaD_rawrunprotected进行保护调用的,即使执行出错,也不会造成整个程序的退出。

这就是Lua协程中,比一般的Lua操作过程做的更多的地方。

最后给出一个Lua协程的例子:
co.lua

  1. print("before")
  2. test("123")
  3. print("after resume")

co.c

  1. #include
  2. #include "lua.h"
  3. #include "lualib.h"
  4. #include "lauxlib.h"
  5. static int panic(lua_State *state) {
  6. printf("PANIC: unprotected error in call to Lua API (%s)\n",
  7. lua_tostring(state, -1));
  8. return 0;
  9. }
  10. static int test(lua_State *state) {
  11. printf("in test\n");
  12. printf("yielding\n");
  13. return lua_yield(state, 0);
  14. }
  15. int main(int argc, char *argv[]) {
  16. char *name = NULL;
  17. name = "co.lua";
  18. lua_State* L1 = NULL;
  19. L1 = lua_open();
  20. lua_atpanic(L1, panic);
  21. luaL_openlibs( L1 );
  22. lua_register(L1, "test", test);
  23. lua_State* L = lua_newthread(L1);
  24. luaL_loadfile(L, name);
  25. lua_resume(L, 0);
  26. printf("sleeping\n");
  27. sleep(1);
  28. lua_resume(L, 0);
  29. printf("after resume test\n");
  30. return 0;
  31. }

你可以使用coroutine.create来创建协程,协程有三种状态:挂起,运行,停止。创建后是挂起状态,即不自动运行。status函数可以查看当前状态。协程真正强大的地方在于他可以通过yield函数将一段正在运行的代码挂起。

lua的resume-yield可以互相交换数据

  1. co = coroutine.create(function (a, b)
  2.      coroutine.yield(a+b, a-b)
  3. end)
  4. print(coroutine.resume(co, 3, 8))
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号