为何要在多个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项目中找到
skynet源码分析之sharedata共享数据 - RainRill - 博客园
然后云风大佬的实现修改了乱虚拟机关于共享表的gc部分,使用了原来gc表志中的一个位。
关于共享表的gc部分,我是没有修改lua的虚拟机的,但是限制共享表共享之后不在原线程中继续使用(读写),被共享表是否gc取决于主线程中
的代理表是否gc。 被共享表的所有子表同最定级的根表同时gc(除了根表引用,不在任何其它地方被引用)。 不过需要对根表的代理表的引用进行计数。
所有子表如果被访问时创建子代理表的时候 都要对根表的引用加一。 因为再主线程中可能删除了对根表代理表的引用,但是仍然引用这一个子表。
这时还不能让共享表gc。 因为后面再访问子表时将得不到数据。 所以必须得等所有的子代理表都被gc ,才能删除共享表。 所以在每个代理表gc时
需要把根表的引用值减一,只有当引用为0时才真正gc,因为这时该表以及子表不存在代理表在主线程中。后面也不会被访问到。
后记: 因我们项目游戏现有逻辑中存在对共享表的复制操作,会导致出错, 因为共享表的原表中的__index方法会通过代理表指针去查找另外虚拟机中的真正表。
拷贝表中元表的__index方法无法获取之前代理表的指针,导致找不到真正表。 需要通过元表的__metatable方法做限制或者实现一个额外的拷贝方法。
而项目中的clone方法依赖pairs()方法,但是项目中的使用的lua版本较低还不支持 __pairs 元方法。需要更新lua版本到较新的版本。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <map>
#include "../../src/lua.hpp"
extern "C"
{
#include "../../src/ltable.h"
#include "../../src/lobject.h"
#include "../../src/lstate.h"
#include "../../src/lgc.h"
#include "../../src/lapi.h"
#include "../../src/lstring.h"
#include "../../src/lvm.h"
}
std::mutex shareTableMutex;
std::vector<Table*> sharedTableList;
//key是主线程里面的table, value是网络线程的table
std::map<Table*, Table*> sharedTableMap;
lua_State* Lvm1 = nullptr;
typedef struct dt_rectangle_s
{
double left;
double bottom;
double right;
double top;
} dt_rectangle_t;
typedef struct dt_line_s
{
double start;
double end;
} dt_line_t;
static int get_left(lua_State *L)
{
dt_rectangle_t* rect = (dt_rectangle_t*)lua_touserdata(L, 1);
lua_pushnumber(L, rect->left);
return 1;
}
Table* getSharedTable(Table* t)
{
//返回共享给当前Table的table
auto it = sharedTableMap.find(t);
while (it != sharedTableMap.end())
{
return it->second;
}
printf("error: shared table not find!\n");
return nullptr;
}
void createSharedTable(lua_State *L, Table* netTable)
{
lua_newtable(L);
luaL_getmetatable(L, "sharetable");
//lua_getglobal(L, "sharetable");
lua_setmetatable(L, -2);
Table* table = (Table*)lua_topointer(L, -1);
sharedTableMap.insert(std::make_pair(table, netTable));
}
static int global__newindex(lua_State* L)
{
if ( !lua_istable(L, 1)) { return 0; }
Table* t = (Table*)lua_topointer(L, 1);
const TValue* value;
if(lua_isstring(L, 2))
{
size_t len = 0;
const char* key = lua_tolstring(L, 2, &len);
}
else if(lua_isnumber(L, 2))
{
int key = (int)lua_tonumberx(L, 2, NULL);
}
return 0;
}
//c函数版的元方法
static int meta__index(lua_State* L)
{
if ( !lua_istable(L, 1)) { return 0; }
Table* t = (Table*)lua_topointer(L, 1);
const TValue* value;
if(lua_isstring(L, 2))
{
size_t len = 0;
const char* key = lua_tolstring(L, 2, &len);
TString * hashkey = luaS_new(Lvm1, key); //这里访问另一个虚拟机中的库可能存在线程安全隐患
value = luaH_getstr(getSharedTable(t), hashkey);
}
else if(lua_isnumber(L, 2))
{
int key = (int)lua_tonumberx(L, 2, NULL);
value = luaH_getint(getSharedTable(t), key);
}
else
{
value = luaO_nilobject;
}
if (ttisnumber(value))
{
lua_Number n;
int isnum = tonumber(value, &n);
if (!isnum) n = 0;
lua_pushnumber(L, n);
lua_settable(L, -3); //直接设置给当前table,下次不再走__index
lua_pushnumber(L, n);
return 1; //返回当前值
}
else if(ttisstring(value))
{
char* str = svalue(value);
lua_pushstring(L, str);
lua_settable(L, -3);
lua_pushstring(L, str);
return 1;
}
else if(ttisboolean(value))
{
bool bval = !l_isfalse(value);
lua_pushboolean(L, bval);
lua_settable(L, -3);
lua_pushboolean(L, bval);
return 1;
}
else if(ttisnil(value))
{
lua_pushnil(L);
lua_settable(L, -3);
lua_pushnil(L);
return 1;
}
else if(ttistable(value))
{
Table* sharedSubTable = (Table*)hvalue(value);
lua_newtable(L);
Table* table = (Table*)lua_topointer(L, -1);
luaL_getmetatable(L, "sharetable");
//lua_getglobal(L, "sharetable");
lua_setmetatable(L, -2);
lua_pushvalue(L, -1); //需要将新建的table留在栈中作为返回值
lua_insert(L, -4); //将栈顶新建的table移到栈底
lua_settable(L, -3); //将table赋值给我们主线程中的table,下次直接访问
lua_pop(L, 1); //将栈顶的父table弹出,保留新建的子talbe返回
sharedTableMap.insert(std::make_pair(table, sharedSubTable));
return 1;
}
else
{
printf("the value error: %s, %i", __FILE__, __LINE__);
}
//该方案不行TValue会加入本虚拟机的GC过程,并且同时也会被原来的虚拟机GC
//必须将TValude的值从共享虚拟机取出来重新push到当前虚拟机
//setobj2s(L, L->top, value); //将值放在栈顶返回给lua脚本层
// api_incr_top(L); //栈顶+1
// lua_settable(L, -3);
//TValue* slot = luaH_set(L, t, L->top - 3);
// setobj2t(L, slot, L->top - 1);
// invalidateTMcache(hvalue(t));
// luaC_barrierback(L, hvalue(t), L->top-1);
// L->top -= 2;
return 0;
}
/**
调用lua_newuserdata新建一个rectangle对象
*/
static int new_rectangle(lua_State *L)
{
int n = lua_gettop(L);
if (n == 1)
{
int ar = lua_tointegerx(L, 1, NULL);
printf("ar:%d\n", ar);
}
dt_rectangle_t *p = (dt_rectangle_t*)lua_newuserdata(L, sizeof(dt_rectangle_t));
p->left = 1;
p->right = 2;
p->bottom = 3;
p->top = 4;
// 绑定元表
//luaL_getmetatable(L, "tabA");
lua_getglobal(L, "tabA");
lua_pushvalue(L, -1); //元表自身放在栈顶
lua_setfield(L, -2, "__index"); //在原表自己里面查找
lua_pushstring(L, "get_left");
lua_pushcfunction(L, get_left);
lua_settable(L, -3);
lua_setmetatable(L, -2);
return 1;
}
static int get_rect_left(lua_State *L)
{
dt_rectangle_t *p = (dt_rectangle_t*) lua_touserdata(L, -1);
lua_pushnumber(L, p->left);
return 1;
}
/**
调用lua_newuserdata新建一个line对象
*/
static int new_line(lua_State *L)
{
dt_line_t *p = (dt_line_t*)lua_newuserdata(L, sizeof(dt_line_t));
p->start = 100;
p->end = 200;
return 1;
}
static luaL_Reg myfuncs[] = {
{"new_rectangle", new_rectangle},
{"get_rect_left", get_rect_left},
{"new_line", new_line},
{NULL, NULL}
};
extern "C" int luaopen_userdatatest(lua_State *L)
{
luaL_newmetatable(L, "rectangle");
lua_pop(L, 1);
luaL_newmetatable(L, "sharetable");
lua_pushstring(L, "__index");
lua_pushcfunction(L, meta__index);
lua_settable(L, -3);
lua_pop(L, 1);
luaL_newmetatable(L, "gmeta");
lua_pushstring(L, "__newindex");
lua_pushcfunction(L, global__newindex);
lua_settable(L, -3);
lua_pop(L, 1);
luaL_register(L, "userdatatest", myfuncs);
return 1;
}
void createSceondVM(void* vm1)
{
Lvm1 = luaL_newstate();
luaL_openlibs(Lvm1);
luaopen_userdatatest(Lvm1);
if (luaL_dofile(Lvm1,"share.lua") != 0)
{
printf("Load Lua File Error\n");
}
lua_getglobal(Lvm1, "tabB");
if (lua_istable(Lvm1, -1))
{
printf("table\n");
}
Table* shareTable = (Table*)lua_topointer(Lvm1, -1);
lua_pushstring(Lvm1, "name2");
lua_pushstring(Lvm1, "张三");
lua_pushvalue(Lvm1, -3);
lua_insert(Lvm1, -3);
lua_settable(Lvm1, -3);
lua_pop(Lvm1, 1);
lua_getfield(Lvm1, -1, "name2");
const char* str = lua_tolstring(Lvm1, -1, NULL);
printf("name2:%s\n", str);
int n = lua_gettop(Lvm1);
lua_pushcfunction(Lvm1, new_rectangle);
lua_pushnumber(Lvm1, 888);
n = lua_gettop(Lvm1);
lua_call(Lvm1, 1, 1);
n = lua_gettop(Lvm1);
if (lua_isuserdata(Lvm1, -1))
{
printf("Rect created success\n");
}
shareTableMutex.lock();
sharedTableList.push_back(shareTable);
shareTableMutex.unlock();
//清空栈才会GC
lua_pop(Lvm1, lua_gettop(Lvm1));
while(true)
{
int n = lua_gettop(Lvm1);
lua_getglobal(Lvm1, "tabC");
if(lua_istable(Lvm1, -1))
{
lua_getfield(Lvm1, -1, "name");
const char* str = lua_tolstring(Lvm1, -1, NULL);
printf("tabCname:%s\n", str);
lua_pop(Lvm1, 2);
}
else
{
n = lua_gettop(Lvm1);
lua_newtable(Lvm1);
lua_pushstring(Lvm1, "name");
lua_pushstring(Lvm1, "tabCN");
lua_settable(Lvm1, -3);
lua_setglobal(Lvm1, "tabC");
int n2 = lua_gettop(Lvm1);
lua_pop(Lvm1, 1);
}
n = lua_gettop(Lvm1);
lua_getglobal(Lvm1, "update2");
lua_call(Lvm1, 0, 0);
lua_gc(Lvm1, LUA_GCCOLLECT, 0);
int n2 = lua_gettop(Lvm1);
if ( (n2 - n) > 10) {
printf("stack error\n");
}
sleep(1);
}
lua_close(Lvm1); //关闭虚拟机
}
int main()
{
lua_State* L = luaL_newstate();
std::thread t(&createSceondVM, L);
luaL_openlibs(L);
luaopen_userdatatest(L);
if (luaL_dofile(L,"share.lua") != 0)
{
printf("Load Lua File Error\n");
}
//lua_getglobal(L, "dump");
//lua_call(L,0,0);
while (true)
{
if (sharedTableList.size()> 0)
{
if (shareTableMutex.try_lock())
{
Table* shareTable = sharedTableList.back();
sharedTableList.pop_back();
shareTableMutex.unlock();
int n = lua_gettop(L);
lua_getglobal(L, "notifyData");
createSharedTable(L, shareTable);
int n2 = lua_gettop(L);
//lua_pushvalue(L, -2);
lua_call(L, 1, 0);
n2 = lua_gettop(L);
lua_pop(L, n2 - n);
}
}
lua_getglobal(L, "update1");
lua_call(L, 0, 0);
lua_gc(L, LUA_GCCOLLECT, 0);
sleep(1);
}
lua_close(L); //关闭虚拟机
return 0;
}