为何要在多个lua虚拟机间共享table呢, 因为lua是不支持真正的多线程的,Lua中的协程其实也是在单线程中运行的。
所以为了发挥cpu的最大性能,我们需要通过多线程异步执行一些任务。这些任务线程是不会阻塞lua虚拟机线程的执行。
当异步线程执行完成之后就需要把数据或者消息传递给主线程lua虚拟机。 当然数据的传递有很多种方式,比较常见的
就是直接传递一个c++的对象或者 以字符串char*传递一块内存。 但是由于c++对象的数据无法在lua中直接使用所以往往
接收后还需要额外的解析。 如果我们能直接从异步线程中返回一个lua的table给主线程直接使用那就更好了,比如我们在异步线程
加载一个文件并且进行解析成table。或者从网络接收一个较大的json数据包然后解析成table。
首先要想从异步线程返回一个table,我们必须在异步线程也创建一个lua虚拟机。因为lua的api是非线程安全的,我们不能在
另外一个线程中直接调用主线程中的lua虚拟机来做事情。返回table的方式有很多,最完美最直接的方式就是直接将table指针或者
引用交给主线程的虚拟机直接访问,也就是不做任何的数据拷贝的。但是理想虽美好,现实很残酷。 因为lua虚拟机不能保证线程
安全, 而且即使我们将这个共享表从原来的lua虚拟机中完全移出(清除引用,从gc链表删除)加入主虚拟机也不行。会出现表中字段
无法通过键值的方式访问,而只能通过pairs这种遍历方式才能访问。比如原表 t = {name="张三", age=20 }共享后
t.name 可能是空无法访问到"张三"。 因为lua虚拟机对所有段字符串的hash做了缓存。并且生成段字符串hash的算法加入了时间随机
和lua虚拟机本身地址的随机因素。 两个不同虚拟机生成同一个短字符串(name)的hash总是不同的。 所以在表存取的时候拿字符串
的hash作为key去映射到的地址也是不同的。所以直接将表的指针传入另外一个虚拟机去使用是不行的。
现在的实现方案是通过代理表去访问共享表,也就是在主线程虚拟机中创建一个空表,该空表设置了一个原表中实现了__index方法和
__gc方法, 通过index方法去拿到key的字符串或者数字,并且在异步线程虚拟机里面的共享表中取得值,然后设置给主线程虚拟机中
的代理表中,这样主线程的代理表可以正常读写,且所有字段的查询平均分配到了每次key的访问中,如果共享表中没有用到的key是
不会去查询的。而且除了第一次访问是需要通过元方法去取值,后面都直接从本地虚拟机lua表中读取,性能是最高的。比通过userdata的
c方法去访问还要快一点。 然后 通过gc方法可以通知异步线程去释放对共享表的引用。 改方法的对数据量大的表有比较大的好处,可以做到
惰性存取。 对于比较小的数据表或者只访问一次的表(不用缓存)可以考虑直接一次复制过去。 另外该方案要求被共享的表不能存在循环引用。
被共享的表,共享之后不能再进行读写操作。 比较适用的典型就是 json数据转成的lua 表。
这里可以参考云风大佬的博客:源码可以再github的 skynet项目中找到
云风的 BLOG: 不同虚拟机间共享不变的 Table
skynet源码分析之sharedata共享数据 - RainRill - 博客园
然后云风大佬的实现修改了乱虚拟机关于共享表的gc部分,使用了原来gc表志中的一个位。
关于共享表的gc部分,我是没有修改lua的虚拟机的,但是限制共享表共享之后不在原线程中继续使用(读写),被共享表是否gc取决于主线程中
的代理表是否gc。 被共享表的所有子表同最定级的根表同时gc(除了根表引用,不在任何其它地方被引用)。 不过需要对根表的代理表的引用进行计数。
所有子表如果被访问时创建子代理表的时候 都要对根表的引用加一。 因为再主线程中可能删除了对根表代理表的引用,但是仍然引用这一个子表。
这时还不能让共享表gc。 因为后面再访问子表时将得不到数据。 所以必须得等所有的子代理表都被gc ,才能删除共享表。 所以在每个代理表gc时
需要把根表的引用值减一,只有当引用为0时才真正gc,因为这时该表以及子表不存在代理表在主线程中。后面也不会被访问到。
后记: 因我们项目游戏现有逻辑中存在对共享表的复制操作,会导致出错, 因为共享表的原表中的__index方法会通过代理表指针去查找另外虚拟机中的真正表。
拷贝表中元表的__index方法无法获取之前代理表的指针,导致找不到真正表。 需要通过元表的__metatable方法做限制或者实现一个额外的拷贝方法。
而项目中的clone方法依赖pairs()方法,但是项目中的使用的lua版本较低还不支持 __pairs 元方法。需要更新lua版本到较新的版本。
#include
#include
#include
#include
#include
#include