Ameba , 一个简单的 lua 多线程实现
by 云风
at 2011-11-12 19:55:54
original http://blog.codingnow.com/2011/11/ameba_lua_52.html
几个月以前,在我在 blog 上曾谈及 Lua 5.2 的改进。它可以用来实现抢占式多线程 。
周末休息,我把这桩事挖出来娱乐一下,花了一整个晚上做了实现。把 lua 的每个线程锁定在独立的 lua state 中,强迫线程之间通过消息管道的方式通讯。经过测试,Lua 5.2 每个独立的 state 占用的内存很小。通过自定义 alloc 函数可以测算出,一个干净的 32bit state ,不含任何库函数时,占用内存量在 2K 以下(1726 bytes)。如果加载基本库,也仅仅占用不到 4K (3265 bytes)。若把所有 lua 官方标准库加载进来,才会上升到 10K 以上(12456 bytes)。
对于 luajit 2 ,这个基础开销会大一些,最小开销也在 10K 左右 (8058 bytes) 。加上 ffi 达到 30k (31605 bytes)。不过 ffi 可以使 lua 代码直接使用 C 的数据结构,在实际运用中还可以减少内存的使用。
废话不多说,我的代码放在了 github 上 ,有兴趣的同学可自取。
这个娱乐项目命名为 Ameba ,暗示每个代码单位都足够的小,功能简单。它们必须通过很少的 send/recv 和外界通讯。目前,通讯的数据类型仅限于 number boolean 和 string 。
<p>每个线程(ameba)可以调用 ameba 函数分裂一个新的 ameba 出去。调度器为每个 ameba 分配了一个唯一的通讯通道 (chan) 。ameba 自己可以通过自己的全局变量 chan 拿到这个数字。</p>
读写 chan 可以通过 send / recv 两个 api 。如果返回值为 nil ,则表示 chan 已经被关闭。(目前只可能是 ameba 自己退出死亡)。chan 的读写是阻塞的。即,当你向 chan 写入数据(可以是一个或多个)时,若同时对端 ameba 没有在读取数据,那么写入者将被挂起;反之亦然。
recv 只可以收取系统为其分配的 chan 上的数据。send 则可以制定一个数字表示向哪个 ameba 发送信息。
关于实现:
抢先式多线程是用 lua 的 debug hook 实现的。它在一定程度上会降低 lua 代码执行的性能(不到一倍),可以通过修改时间片的粒度来调整。
每个 ameba 里都有一个 chan 读缓冲区,放在注册表中。当有另外的 ameba 企图写入的时候,首先写到这个缓冲区中,再由本地的 recv 函数复制出来。
整个系统的父 state 中保留有一个表,映射了所有 ameba 的读写队列。用于调度器的工作。这张表存在于父 state 的注册表中。
ameba 之间没有父子层级关系。
这个库是设计成可以递归使用的,可以在 ameba 内重新启动库,这样就可以有层级关系。但,chan id 是一个 C 里的全局变量,会一直累加。当然也可以修改实现,把这个变量放在 lua state 中。
目前的实现中,每个 ameba 的 state 只初始化了 lua 的基础库,没有加载完整的标准库。
自定义的 lua alloc 函数未来用于控制内存分配和管理,目前并没有特别使用。
整块代码娱乐性质为主,也就是写着玩儿。不保证质量。
代码必须装有 lua 5.2 beta 版才可能正常编译,经过少许修改也可以用于 luajit 2 beta 。但由于 lua 早期版本的实现限制,无法用于 lua 5.1 或更早的版本。