协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有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即可,它其实是一个宏:

#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协程的状态对不对,然后修改计数器:

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

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

static void resume (lua_State *L, void *ud) {
StkId firstArg = cast(StkId, ud);
CallInfo *ci = L->ci;
if (L->status == 0) { /* start coroutine? */
lua_assert(ci == L->base_ci && firstArg > L->base);
if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
return;
}
else { /* resuming from previous yield */
lua_assert(L->status == LUA_YIELD);
L->status = 0;
if (!f_isLua(ci)) { /* `common' yield? */
/* finish interrupted execution of `OP_CALL' */
lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
if (luaD_poscall(L, firstArg)) /* complete it... */
L->top = L->ci->top; /* and correct top if not multiple results */
}
else /* yielded inside a hook: just continue its execution */
L->base = L->ci->base;
}
luaV_execute(L, cast_int(L->ci - L->base_ci));
}

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

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

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

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

继续回到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

print("before")
test("123")
print("after resume")

co.c

 #include
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h" static int panic(lua_State *state) {
printf("PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(state, -1));
return 0;
} static int test(lua_State *state) {
printf("in test\n");
printf("yielding\n");
return lua_yield(state, 0);
} int main(int argc, char *argv[]) {
char *name = NULL;
name = "co.lua";
lua_State* L1 = NULL;
L1 = lua_open();
lua_atpanic(L1, panic);
luaL_openlibs( L1 ); lua_register(L1, "test", test);
lua_State* L = lua_newthread(L1); luaL_loadfile(L, name);
lua_resume(L, 0);
printf("sleeping\n");
sleep(1);
lua_resume(L, 0);
printf("after resume test\n"); return 0;
}

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

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

co = coroutine.create(function (a, b)
     coroutine.yield(a+b, a-b)
end)
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. maven 配置jdk版本编译

    在maven的默认配置中,对于jdk的配置是1.4版本,那么创建/导入maven工程过程中,工程中未指定jdk版本. 对工程进行maven的update,就会出现工程依赖的JRE System Lib ...

  2. Html 表单标签 Form

    Html表单 #转载请留言联系 表单用于搜集不同类型的用户输入,表单由不同类型的标签组成,相关标签及属性用法如下: 1.<form>标签 定义整体的表单区域 action属性 定义表单数据 ...

  3. JAVA学习笔记及知识积累

    为什么说Java具有跨平台特性? 我们知道计算机只认识1,0两种电平的信号,所有信息或者计算指令最终都编码成16进制的机器码,这些机器码作为程序保存于计算机的内存中,由CPU去单个取指令执行直到程序执 ...

  4. php各种主流框架的优缺点

    ThinkPHP ThinkPHP(FCS)是一个轻量级的中型框架,是从Java的Struts结构移植过来的中文PHP开发框架.它使用面向对象的开发结构和MVC模式,并且模拟实现了Struts的标签库 ...

  5. 我的Java之旅——答答租车系统

    今天试着写了一个新的程序,叫做"答答租车系统",是慕课网上的一个综合练习,戳我看原题. 项目要求截图如下: 我的代码(简单粗暴版): Vehicle.java public cla ...

  6. TeamViewer app案例分析

    产品 产品名 TeamViewer远程app 选择原因 远程连接软件是不时之需,当有时私人电脑没有在身边而又需要操作电脑时,远程控制TeamViewer这个软件能帮我们大忙. 调研与评测 1.第一次上 ...

  7. 【hibernate学习杂记】维护关系的一方和不维护关系的一方的区别与联系

    双向多对一/一对多例子 维护关系一方为User:多方 不维护关系的一方为Group:一方 以下是多方代码: package Hibernate_demo1.Demo8.Entity.OneToMany ...

  8. 4-1 R语言函数 lapply

    #lapply函数 #可以循环处理列表中的每一个元素 #lapply(参数):lapply(列表,函数/函数名,其他参数) #总是返回一个列表 #sapply:简化结果 #结果列表元素长度均为1,返回 ...

  9. FFI (语言交互接口(Foreign Function Interface))

    FFI(Foreign Function Interface)是用来与其它语言交互的接口, 在有些语言里面称为语言绑定(language bindings), Java 里面一般称为 JNI(Java ...

  10. [JSOI2008]火星人

    嘟嘟嘟 嗯. splay维护哈希. 如题,用splay维护哈希,查找的时候二分.所以复杂度是取决于询问复杂度:\(O(n \log^ 2{n})\). 这道题还有一个技巧,就是一个节点记录的是他的子树 ...