协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有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) \
  2. (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))

lua协程实现的更多相关文章

  1. Lua 协程coroutine

    协程和一般多线程的区别是,一般多线程由系统决定该哪个线程执行,是抢占式的,而协程是由每个线程自己决定自己什么时候不执行,并把执行权主动交给下一个线程. 协程是用户空间线程,操作系统其存在一无所知,所以 ...

  2. [转]-Lua协程的实现

    协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制:另 外,协程也更加轻量级.这样,在遇到某些可能阻塞的操作时,可以使 ...

  3. LUA 协程

    LUA协程和C#协程非常相似,功能与用法更强大.基础用法: coco = coroutine.create(function (a,b) print("resume args:". ...

  4. lua 协程的理解

    参考链接: http://www.cnblogs.com/zrtqsk/p/4374360.html 对例子的自我理解: -- 协程的理解 -- co 是协程的内容,类似函数内容, 通过yield 将 ...

  5. Lua协程-测试3

    print("Lua 协程测试3") -- 实现消费者-生产者关系(生产一个就消费一个) count = -- 生产总数 -- 生产者 local newProductorCo = ...

  6. Lua协程-测试2

    print("Lua 协程测试2") function testFun(n) print("into foo,n = "..n) * n) -- 挂起co协程 ...

  7. 大富翁开发日记:一、使用巨型lua协程

    一个大胆的尝试:使用巨型lua协程来表示整个“一局”流程. lua协程是一个很另类的功能,有并发的影子但又不是真的并发,所以真正拿它来做大功能框架的范例不多,通常用于一些小型trick式设计.但这次我 ...

  8. Lua 协程和线程区别

    协程就是协程,不是线程. CPU执行单位是线程,不是什么协程. 协程,是同步执行,不是并行,只是切了一个上下文了,为你保存原来的上下文而已. 切到第二个协程时,原来的协程处于挂起状态. 这个特指lua ...

  9. Openresty Lua协程调度机制

    写在前面 OpenResty(后面简称:OR)是一个基于Nginx和Lua的高性能Web平台,它内部集成大量的Lua API以及第三方模块,可以利用它快速搭建支持高并发.极具动态性和扩展性的Web应用 ...

随机推荐

  1. java 内存分析之构造方法执行过程

    package Demo; public class BirthDate { private int day; private int month; private int year; public ...

  2. web项目启动时,自动执行代码的几种方式

    在项目开发过程中,往往需要一些功能随着项目启动而优先启动,下面我总结几种方式(非spring boot) spring boot的参考 spring boot 学习之路9 (项目启动后就执行特定方法) ...

  3. MQTT介绍(1)简单介绍

    MQTT目录: MQTT简单介绍 window安装MQTT服务器和client java模拟MQTT的发布,订阅 MQTT: MQTT(Message Queuing Telemetry Transp ...

  4. 【眼见为实】自己动手实践理解数据库READ UNCOMMITED && SERIALIZABLE

    目录 准备工作 ①准备测试表和测试数据 ②关闭数据库事务自动提交 ③设置InnoDB存储引擎隔离级别 [READ UNCOMMITTED] [READ UNCOMMITTED]能解决的问题 [READ ...

  5. 团队项目个人进展——Day02

    一.昨天工作总结 冲刺第二天,昨天成功接入高德地图接口,并通过官方提供的文档实现了地图定位功能,通过官方在GitHub上提供的Demo可以看出还能实现天气查看,路径规划等功能: 二.遇到的问题 虽然通 ...

  6. 探索ORM之iBati(一)

    ibatis   iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目.最初侧重于密码软件的开发,现在是一个基于Jav ...

  7. [SQLServer] 内存占用查看

    SELECT (physical_memory_in_use_kb/1024) AS Memory_usedby_Sqlserver_MB, (locked_page_allocations_kb/1 ...

  8. springmvc常用的组件,注解,跳转

    路径映射 XXXHandlerMapping 随开发配置越来越多 注解到java代码中来简化xml配置 请求到哪个Controller 控制器bean Controller 随着开发配置越来越多 注解 ...

  9. ZT 人生真的是一场马拉松吗?

    中国合伙人:孟晓俊:生活应该是什么样的?自己提出的问题应该由自己来回答,别人的回答是别人的答案,是别人的生活,而你应该过自己的生活,不是别人的生活.     人生真的是一场马拉松吗? 投递人 itwr ...

  10. 映射函数map

    映射函数map 语法: map(function, iterable) 迭代对象中 的每一个元素进行映射, 分别执行function函数 例子:  ls =[1,2,3,4,5,6] def func ...