Lua 下实现抢占式多线程

标签: lua与虚拟机 | 发表时间:2011-08-09 18:52 | 作者:云风 Coder(码农)
出处:http://blog.codingnow.com/

Lua 5.2 的开发进度可以回溯到 2010 年 1 月。漫长的流程到今天已经快两年过去,终于等到了 beta 版。我十分期待它可以在 2011 年内正式发布。在这几经折腾的两年里,许多新特性企图挤进 5.2 版,又最终被否决。

当我们审视改进列表,似乎看不到太多耳目一新的东西。但如果仔细阅读一下源代码,就会发现,大部分地方都重新实现过了,以配合这些表面上看起来不大的修改。如果你对 Lua 有足够理解,会发现,这次最激动人心的改进是 "yieldable pcall and metamethods" 。官方也把之列为 Main changes 第一条。语言上的重大新特性 goto 却被列在末尾。

当然,这只是我粗浅的理解而已。没有经过实践使用 5.2 一段时间,下这样的论断有点太草率。不过我还是想谈谈,这点改进可以给我们的开发带来什么。

coroutine 的 yield 现在几乎可以在任何地方使用了。我用了几乎,是因为它依然有一些限制。这些限制不大容易说的很清楚,为了理解其限制,我花了一整天实现阅读 lua 5.2 beta 版的源代码。这个话题下次有机会我再另写一篇 blog 总结一下。今天只谈应用。

我们知道 coroutine 可以实现一个协同多线程模型。即,每个线程(coroutine) 只在用户期望的地方跳出来,并可以在以后跳回去(保持当初跳离的状态)。这解决了许多抢占式多线程的麻烦。lua 的发明人在一篇访谈中谈到了 coroutine 解决并发的问题。但是,让用户不厌其烦的写 yield 是件很讨厌的事情。往往框架会把 yield 调用藏起来。如果我没记错,我读过的早期的 kepler 框架就是把 yield 藏在 html 的输出里的。能够想到的更漂亮的做法是编写一个 lua debug hook ,在 hook 里调用 yield 。这样就可以让 lua vm 每跑几行 lua byte code 就自动做 yield 一次。

可惜的是,lua 5.1 以前的版本不支持这个。因为 yield 限制太多了。如果恰巧 yield 发生在一次 metamethod 调用内部,或 pcall 内部,就会失败。这和 C/Lua 函数嵌套有关。lua vm 在实现 pcall 及 metamethod 机制时,不断的在 C 函数以及 lua 函数间跳转。一旦 yield 发生在多层 C 函数与 lua 函数嵌套调用的内部。用 longjmp 返回的 yield 机制,会丢失 C 函数调用的 stack frame 。也就是说,lua vm 本身的状态可以保留在 L 中,但 C 函数的状态却丢失了,无法正确返回。Lua 5.2 为了解决这个问题,引入了新的 api ,有兴趣可以阅读新的文档的 4.7 – Handling Yields in C 。不过真的想全部搞清楚,还是推荐阅读一下源代码。

只使用系统自带的 debug 库是不够的。用 lua 的 debug.sethook 设置一个 lua 函数做 hook ,里面依然不能调用 coroutine.yield 。这还是受到了 lua 实现的限制。正确的方法是直接使用 C api lua_sethook,把这样一个 hook 函数设进去:

static void
hook(lua_State *L, lua_Debug *ar)
{
    lua_yield(L,0);
}

至于你想让它每隔几行 lua 代码 yield 一次,还是让它发生在函数调用的时候,这看你的喜好。但一旦设置成功,一段 lua 代码就可以透明的定期 yield 出来了。

这些有什么用?我设想了两种用法,

一、是用 lua 做一个调度器,模拟出一个抢占式的多线程库。本质上,这个库是基于 coroutine 实现的。但切换线程是利用的 debug hook 定期强制切换。基于这个库,可以把以前我写过的这个小玩意改进一下。做的更易用一些。

二、是在一个 os 进程内启动多个独立的 lua state ,每个 lua state 并不包含多个 coroutine ,而只用一个 main thread 。设置 debug hook ,让 main thread 可以每跑一个阶段就 yield 出来,把控制权交还到上层的 C 代码。在 C 层次写一个有效的调度器。对 lua state 来说,每个都是独立的,它们之间可以通过 zeromq 这样的库通讯。lua state 还可以部署到多个 os thread 上,实现一个 M:N 的线程模型。它的调度器会比 os 来的更为高效。(关于在语言级实现 M:N 线程模型和 OS 级实现 M:N 线程序模型的性能差异,上个月 7 月 12 日我们在 google+ 上做过一次讨论。可惜是在有限圈子里做的,暂时没找到方法公开)且少占用大量的堆栈资源。

话说到这里,再看看,其实这不就是 Erlang 的模型么?:)

ps. 其实 lua 的早期版本也可以通过 lua coco 实现无限制的 yield 操作。但 coco 使用了 OS 的 fiber 库 这比 5.2 版的 lua 实现多出了额外的堆栈开销。


一些还没实现的想法:游戏服务器里的 npc ai 等东西是不是可以放在一个个独立的 lua state 中完成?它们相互不影响,用消息交互。可以一开始分配好一个 state 池,用来动态绑定新的 AI 单位(减少启动初始化的代价)。把任务切分成独立的小单位,在独立的 lua state 中做,我想对 lua 的 gc 操作也是极为有利的。

相关 [lua 多线程] 推荐:

Lua 下实现抢占式多线程

- Coder(码农) - 云风的 BLOG
Lua 5.2 的开发进度可以回溯到 2010 年 1 月. 漫长的流程到今天已经快两年过去,终于等到了 beta 版. 我十分期待它可以在 2011 年内正式发布. 在这几经折腾的两年里,许多新特性企图挤进 5.2 版,又最终被否决. 当我们审视改进列表,似乎看不到太多耳目一新的东西. 但如果仔细阅读一下源代码,就会发现,大部分地方都重新实现过了,以配合这些表面上看起来不大的修改.

5本Lua免费电子书

- sospartan - 读写网 ReadWriteWeb
在最新的编程语言排名中,Lua超过了JavaScript进入了前十名──许多人使用Lua进行“魔兽世界”的脚本编写. 所以,在本周的免费资源推荐中我们找到了一些免费的学习Lua的电子书,无论你想使.

编写高性能的Lua代码

- - 九点 科技
Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面,尤其是游戏. 像《魔兽世界》的插件,手机游戏《大掌门》《神曲》《迷失之地》等用Lua来写游戏逻辑. 所以大部分时候我们不需要去考虑性能问题. Knuth有句名言:“过早优化是万恶之源”. 其意思就是过早优化是不必要的,会浪费大量时间,而且容易导致代码混乱.

nginx+lua+memcache实现灰度发布

- - 开源软件 - ITeye博客
灰度发布在百度百科中解释:. 灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式. AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来. 灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度.

采访 Lua 发明人的一篇文章

- KK - 云风的 BLOG
《Masterminds of Programming: Conversations with the Creators of Major Programming Languages》是本相当不错的书. 博文翻译出版了这本书,中文名叫做《编程之魂》. 书是好书,可惜翻译这本书需要对各种语言的深入研究,看起来译者有点力不从心.

lua中处理变参数的一些技巧

- return - 为着理想勇敢前进
从lua 5.1开始,就不鼓励用隐含的arg来处理可变变量了,推荐做法是使用递归来处理.... 在lua中,不论多返回值还是函数调用,...都只能用于最后剩下的参数,例如下列代码中. 这是因为第一个print调用是把...作为剩余的所有参数传进去,而第二次调用,...则被调整为1个参数. 如果要想把参数添加到...后面,有两种做法.

开发愤怒的小鸟的Lua语言:Wax框架详解

- sun - Starming星光社最新更新
摘要:我们都知道Objective-C和Cocoa语言可以开发iOS应用,但是一年前,苹果决定在 iOS系统上使用Lua语言. Wax框架的想法很简单:凡是Objective-C能做的,Lua也能做. 考虑使用像Lua这样一门简单而高效的编程语 言,构建原生iPhone应用程序有许多充分的理由,而本文将深入探讨Wax具有的一些好处,同时演示把Lua与Xcode 4和iOS软件开发工具包(SDK)集成起来必不可少的实际步骤.

使用varnish + nginx + lua搭建网站的降级系统

- - 博学无忧
通常一个网站数据库挂掉后,后果将是非常严重的. 对于一些网站来说,当数据库挂掉后,如果能提供基本的浏览服务,也是不错的. 本文将尝试使用varnish + nginx + lua 搭建网站降级系统来实现整个目标. 降级方案的目标是,当网站出现致命故障时(如出现500错误,不能提供服务),可以把缓存的页面数据展现给用户.

使用 Lua 编写一个 Nginx 认证模块

- - 开源软件 - ITeye博客
我考虑了几种解决方案,罗列如下:. 用一个简单的Python/Flask模块来做代理和验证. 一个使用subrequests做验证的nginx模块(nginx目前可以做到这一点). 使用Lua编写一个nginxren认证模块. 很显然,给整个系统添加额外请求将执行的不是很好,因为这将会增加延迟(特别是给每一个页面文件都增加一个请求是很让人烦恼的).这就意味着我们把 subrequest模块排除在外了.

基于Lua+Kafka+Heka的Nginx Log实时监控系统

- - 开源软件 - ITeye博客
在我们的系统架构中,Nginx作为所有HTTP请求的入口,是非常重要的一层. 每天产生大量的Nginx Access Log,闲置在硬盘上实在是太浪费资源了. 所以,能不能把Nginx日志利用起来,实时监控每个业务的访问趋势、用户行为、请求质量和后端异常呢,这就是本文要探讨的主题. 错误码告警(499、500、502和504);.