lua协程实现
协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制;另外,协程也更加轻量级。这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU;而当条件满足时,可以继续执行这个协程。目前在网络服务器领域,使用Lua协程最好的范例就是ngx_lua了
来看看Lua协程内部是如何实现的。
本质上,每个Lua协程其实也是对应一个LuaState指针,所以其实它内部也是一个完整的Lua虚拟机—有完整的Lua堆栈结构,函数调用栈等等等等,绝大部分之前对Lua虚拟机的分析都可以直接套用到Lua协程中。于是,由Lua虚拟机管理着这些隶属于它的协程,当需要暂停当前运行协程的时候,就保存它的运行环境,切换到别的协程继续执行。很简单的实现。
来看看相关的API。
- lua_newthread
创建一个Lua协程,最终会调用的API是luaE_newthread,Lua协程在Lua中也是一个独立的Lua类型数据,它的类型是LUA_TTHREAD,创建完毕之后会照例初始化Lua的栈等结构,有一点需要注意的是,调用preinit_state初始化Lua协程的时候,传入的global表指针是来自于Lua虚拟机,换句话说,任何在Lua协程修改的全局变量,也会影响到其他的Lua协程包括Lua虚拟机本身。
- 加载一个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函数做的事情其实有那么几件:
- 如果调用C函数时被YIELD了,则直接返回
- 如果之前被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协程实现的更多相关文章
- Lua 协程coroutine
协程和一般多线程的区别是,一般多线程由系统决定该哪个线程执行,是抢占式的,而协程是由每个线程自己决定自己什么时候不执行,并把执行权主动交给下一个线程. 协程是用户空间线程,操作系统其存在一无所知,所以 ...
- [转]-Lua协程的实现
协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制:另 外,协程也更加轻量级.这样,在遇到某些可能阻塞的操作时,可以使 ...
- LUA 协程
LUA协程和C#协程非常相似,功能与用法更强大.基础用法: coco = coroutine.create(function (a,b) print("resume args:". ...
- lua 协程的理解
参考链接: http://www.cnblogs.com/zrtqsk/p/4374360.html 对例子的自我理解: -- 协程的理解 -- co 是协程的内容,类似函数内容, 通过yield 将 ...
- Lua协程-测试3
print("Lua 协程测试3") -- 实现消费者-生产者关系(生产一个就消费一个) count = -- 生产总数 -- 生产者 local newProductorCo = ...
- Lua协程-测试2
print("Lua 协程测试2") function testFun(n) print("into foo,n = "..n) * n) -- 挂起co协程 ...
- 大富翁开发日记:一、使用巨型lua协程
一个大胆的尝试:使用巨型lua协程来表示整个“一局”流程. lua协程是一个很另类的功能,有并发的影子但又不是真的并发,所以真正拿它来做大功能框架的范例不多,通常用于一些小型trick式设计.但这次我 ...
- Lua 协程和线程区别
协程就是协程,不是线程. CPU执行单位是线程,不是什么协程. 协程,是同步执行,不是并行,只是切了一个上下文了,为你保存原来的上下文而已. 切到第二个协程时,原来的协程处于挂起状态. 这个特指lua ...
- Openresty Lua协程调度机制
写在前面 OpenResty(后面简称:OR)是一个基于Nginx和Lua的高性能Web平台,它内部集成大量的Lua API以及第三方模块,可以利用它快速搭建支持高并发.极具动态性和扩展性的Web应用 ...
随机推荐
- 修改spfile位置
虽然很多地方不建议这么做,可是有HA.oracle软件建在本地盘的情况下,如果spfile放在dbs下,会导致每次修改spfile都要去手动copy到备机上,这是很麻烦的一件事情,所以我把spflie ...
- 回归JavaScript基础(一)
主题:JavaScript简介. 一.JavaScript的起源 JavaScript诞生于1995年.当时,它的主要作用是处理一些输入验证操作.之前的话,都是把表单数据发送到服务器端,然后再去判断有 ...
- 测试、集成等领域最好的Java工具
无论你是刚入门,还是进行了一段时间的开发,使用合适的工具编程都会让你事半功倍,它能够让你更快的编写代码,能够快速及时的为你识别出Bug,能够让你的代码质量更上一层楼. 如果你选择的编程语言是Java, ...
- Mysql使用binlog恢复数据解决误操作问题的两种方法
为保证没有其他参数配置影响,重新安装配置了一台最小化安装的CentOS7虚拟机 1. 基础知识 安装mysql5.6数据库Mysql binlog初步理解 2. 配置mysql 开启binlog.修 ...
- linux面试总结
一.填空题:1. 在Linux系统中,以 文件 方式访问设备 .2. Linux内核引导时,从文件 /etc/fstab 中读取要加载的文件系统.3. Linux文件系统中每个文件用 i节点 来标识. ...
- Alpha 冲刺报告(2/10)
Alpha 冲刺报告(2/10) 队名:洛基小队 团队困难汇总:在开始正式编码的时候遇到了很严重的问题,Cocos Creator的教程过少,之前浏览的官网上的教程以为很齐全,但是在最重要的脚本方面还 ...
- python3 实现细胞自动机
废话不多说,先直接上效果图: “滑翔者”:每4个回合“它”会向右下角走一格.虽然细胞早就是不同的细胞了,但它能保持原本的形态. "脉冲星":它的周期为3,看起来像一颗周期爆发的星星 ...
- linux_bc命令
bc 命令: bc 命令是用于命令行计算器. 它类似基本的计算器. 使用这个计算器可以做基本的数学运算. 语法: 语法是 bc [命令开关]命令开关: -c 仅通过编译. ...
- Golang包管理工具glide简介
Golang包管理工具glide简介 前言 Golang是一个十分有趣,简洁而有力的开发语言,用来开发并发/并行程序是一件很愉快的事情.在这里我感受到了其中一些好处: 没有少了许多代码格式风格的争论, ...
- Hive学习之路 (十二)Hive SQL练习之影评案例
案例说明 现有如此三份数据:1.users.dat 数据格式为: 2::M::56::16::70072, 共有6040条数据对应字段为:UserID BigInt, Gender String, A ...