用栈方式管理 Lua 中的 C 对象
最近思考了给 Lua 写 C 扩展的另一个问题。
我曾经总结过几种 Lua C 库中 C/C++ 对象的生命期管理问题 。最近想到另一个方案,虽然实现后并没有用到项目里,但值得记录一下。
Lua 没有 RAII ,一切对象的回收是依赖 GC 的。封装 C/C++ 对象则一般用 userdata 。userdata 比较重,作为临时对象使用总觉得有点别扭。比如封装 matrix 对象,如果我们为每个 matrix 对象都生成一个 userdata ,那么一些临时的 matrix 对象就会一直推迟到 GC 发生的时候才回收。而在 C/C++ 这样的语言中,临时对象通常是在离开调用层次时自动释放的。
对于某些 C 和 Lua 混合的业务也有这样的问题。某些较长的业务流程,一部分环节由于性能原因使用 C 来实现,另一部分更适合直接用 Lua 。我们必须用 userdata 来交换中间状态。比如处理一个 C 层次上产生的数据包或 C 结构数据,交由 Lua 处理后,C 对象就没有必要再存在了。但处理过程中,Lua 代码则需要反复引用和处理它。
多数情况下,我们不用太考虑这两者间的差别。但这并不妨碍我去考虑有没有可能在 Lua 中模拟一套栈对象的管理机制。它可能是 GC 系统之外的一种对象生命期管理的选择。
我试着用 C 实现了一个简单的栈结构,每个堆栈用一个数组来保存一系列相同的 C 对象(或指针)。由于 Lua 的 coroutine 的存在,没有 lua thread 都应该有一个独立的栈,所以我把这个结构封装成 userdata 放在一张表里,用当前的 lua thread (即 lua State 对象)索引。
用户可以主动的调用入栈或出栈,这有一点繁琐,可以考虑放在 debug hook 中实现,但我觉得手工调用更好。如果入栈和出栈调用不匹配的话,栈深度就很容易变成负数或超过设置的最大值。所以一旦写错,很容易被发现。
索引栈上的对象可以用一个组合起来的 id ,高位使用 stack frame 的编号,低位使用序号。这个 id 可以对应到唯一的对象。我采用单调递增的 stack frame 编号,也就是说,及时在同一层次的 stack frame 上,多次函数调用在同一位置产生的对象也有不同的 id 。
这个 id 就可以以 lightuserdata 的形式保存在 Lua 中的。Lua API 引用 lightuerdata 的时候,可以校验 id 是否还在当前栈上,并得到真正的对象。当离开调用层次时,C 库去释放掉那些过期的对象。
Comments
Posted by: Cloud | (5) June 19, 2013 03:10 PM
Posted by: middleware | (4) June 19, 2013 11:27 AM
Posted by: 400电话 | (3) June 13, 2013 03:56 PM
Posted by: 地球人 | (2) June 11, 2013 01:10 PM
Posted by: qiaojie | (1) June 11, 2013 01:01 AM