lua不同虚拟机共享table的实现

       为何要在多个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 

#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 sharedTableList;

//key是主线程里面的table, value是网络线程的table
std::map 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;
    
}

你可能感兴趣的:(lua,lua,开发语言)