针对在 Skynet 间数据的共享,有了 ShareTable,但是他有一个缺点,就是无法共享函数。因此,我们如何把相同的函数逻辑在不同的虚拟机服务间共享,需要好好的想想。关于 ShareTable 可以看这里 Skynet中服务间的数据共享方案-Sharetable
非常规方式?
在 Sharetable 服务的构造中,我看到了云风利用一个函数,来构造一个服务的方式。具体利用 string.dump 来将一个函数 dump 成二进制的格式,然后发送到其他服务去,进行执行,构造服务:请看这里的代码,本质上就是利用了 string.dump。那么,我们是不是也可以将我们需要共享的函数这样操作过去呢。在 Github 上提问了之后,作者给出了答复:
sharetable 是一个 trick ,用来解决不变数据的共享时性能问题。它不应该变成同步数据的常规手段。
解决问题应该优先考虑常规手段,你这个问题用 rpc 把需要的数据传递过去同步就够了。
因为暂时放弃了这种想法。
缓存?
另外,在云风的文章中看到:
skynet 修改了 Lua 的官方实现(可选),加入了一个新特性,可以让多个 Lua VM 共享相同的函数原型1。当在同一个 skynet 进程中开启了大量 lua VM 时,这个特性可以节省不少内存,且提高了 VM 启动速度。
这个特性的使用,对一般用户来说是透明的。它改写了 lua 的辅助 API
luaL_loadfilex
,所有直接或间接调用这个 api 都会受其影响。比如:loadfile 、require 等。它以文件名做 key ,一旦检索到之前有加载过相同文件名的 lua 文件,则从内存中找到之前的函数原型替代。注:Lua 函数是由函数原型以及 0 或多个 upvalue 绑定而成。loadstring 不受其影响。所以,如果你需要多次加载一份 lua 文件,可以使用 io.open 打开文件,并使用 load 加载。
代码缓存采用只增加不删除的策略,也就是说,一旦你加载过一份脚本,那么到进程结束前,它占据的内存永远不会释放(也不会被加载多次)。在大多数情况下,这不会有问题。
skynet 留出了接口清理缓存,以做一些调试工作。接口模块叫做 skynet.codecache 。
这个问题能够实现的关键在于:
我们知道,lua 里的 function 是 first-class 类型的。lua 把函数称为 closure ,它其实是函数原型 proto 和绑定在上面的 upvalue 的复合体。对于 Lua 实现的函数,即使没有绑定 upvalue ,我们在语言层面看到的 function 依然是一个 closure ,只不过其 upvalue 数量为 0 罢了。
btw, 用 C 编写的 function 不同:不绑定 upvalue 的 C function 被称为 light C function ,可视为只有原型的函数。
如果函数的实现是一致的,那么函数原型就也是一致的。无论你的进程中开启了多少个 lua 虚拟机,它们只要跑着一样的代码,那么用到的函数原型也应该是一样的。只不过用 C 编写的函数原型可以在进程的代码段只存在一份,而 Lua 编写的函数原型由于种种原因必须逐个复制到独立的虚拟机数据空间中。
这里又区别了几个内容:
- C function 有上值(upvalue) 的 C 函数
- Light C function 上值数量为0 的 C 函数。我们从 5.4 文档也可以看到 Pushes a C function onto the stack. This function is equivalent to
lua_pushcclosure
with no upvalues. - Lua function. Lua 编写的函数。
因为 Lua 每加载一个文件就会返回一个函数,因此 云风在此做了点修改,用这个加载文件返回的函数原型来判断是否进行缓存(共享)。
为了更好的利用这个特性,我在 skynet 中,改写了
luaL_loadfilex
。这个 patch 版的文件加载函数是线程安全的。它为每个文件名对应的函数(lua 中加载一个源文件,就生成一个函数)创建一份独立的 lua 虚拟机,并将生成好的函数原型指针记录下来。之后同名文件的加载就不再有文件 IO ,不必再次解析文件,直接用lua_clonefunction
复制一份出来。
我们可以来看看这段代码:
LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, |
事实上,我们利用这个特性,就可以做到共享代码文件了。但是,这个仅仅是解决了代码加载的问题,所以它叫 codecahe,而不叫 codeshare。
当缓存的文件中的代码,依赖于一些虚拟机内部状态的时候,就有可能并不能达到想要的结果,因此,尽量不要依赖一些虚拟机状态的值(如全局变量)。