lua多线程解决方案

直观的讲:lua并不支持多线程,lua语言本身具有携程功能,但携程仅仅是一种中继器。

lua多线程的目的:有并发需求时,共享一些数据。

例如使用lua写一个并发服务器。用户登陆之后,用户数据储存在lua中,这样网络IO层与协议控制层可以由C来做,而业务逻辑可以完全交给lua。

解决方案共3种:

1、基于lua_newthread创建lua子对象,重定义lua源码中的lua_lock与lua_unlock宏。

优点:这种方案的外表是完美无缺的。

缺点:降低lua的整体运行速度,因为我们使用了加锁的方式去维护lua的gc机制,并且我个人认为代价很大。

这种方案是我最初设计方案,但由于不能忍受一次请求上百次加锁操作,我最终放弃了这个方案。

https://blog.csdn.net/Gnorth/article/details/102565069

2、改造lua源码,实现从最底层直接绕开lua垃圾回收机制,单独划分一块内存区域出来储存共享数据。

优点:性能最高,除了跨对象调用函数无法实现,其他数据存取都能够实现。

缺点:实现难度非常大,如果你能够实现出来,你已经具备开发一门具有虚拟机的脚本语言的能力了。

这种方案是我在尝试了由脚本里的table元表配合简单的C接口去一个公共的lua_State之后,而考虑的解决方案,但最终也放弃了。

这种方案我暂时没办法完整的发出来,因为改造的代码太多,从阅读lua源码直到改造成功,共经历6天,每天至少10小时以上,然而我最后也放弃了这个方案,因为我认为这个功能应该由lua官方来实现,而不该由我来越俎代庖,因为要求有这个需求的人都必须读懂lua源码本身就不现实。

3、将共享数据存储在C或一个公共的lua_State对象中,利用lua元表实现共享table存取逻辑。

优点:具有最高的可维护性,性能在可接受范围内。

缺点:局限性最大。

这是我目前正在使用的方案,如下:

#include <thread>
#include <iostream>
#include <mutex>
#include <string>
#include <unordered_map>
#include <variant>
#include "lua/lua.hpp"
#pragma comment(lib, "lua53.lib")

//得益于c++17 的 std::unordered_map与std::variant,在C++里不需要考虑实现hash储存逻辑,std::variant既然已经出现在标准中,它自然是已经支持了hash算法了
//代码封装得并不好看,但是性能基本无损,
//另外,涉及到lua table与C C++交互,其实我有好好琢磨过这件事,很多时候,其实只有按自己的思路去琢磨,才看得清代码,看别人的代码,其实大多数时候都挺诡异的。

struct xshare_table;

//为了避免intptr_t在32位的情况下与lua C API的BOOL冲突,所以使用了无符号1字节的类型来储存lua的boolean
using xshare_bool = unsigned char;


//共享数据只支持5种数据类型:字符串, 整数, 浮点数,boolean, table
using xshare_variant = std::variant<std::string, intptr_t, double, xshare_bool, xshare_table*>;

//共享table的key,只支持3种数据类型:字符串, 整数以及浮点数
using xshare_key = std::variant<std::string, intptr_t, double>;

using xshare_type = std::unordered_map<xshare_key, xshare_variant>;
struct xshare_table {
        /*每一层table,都提供了递归锁,
        lua中使用xshare.lock(tab) 与 xshare.unlock(tab)来进行并发控制
        这么做并不能实现lua里任意访问数据都在加锁的情况下进行,因为lua访问table元素是一层一层的递进的,而不是一串的直接进来。
        所以,这个样子其实只是因为我懒得仔细写,另外,每层都有锁的情况下,只要在lua中也能简单的实现不同情况的互斥,
        只要你保证别把某一层nil掉,其实每一层的共享table副本是可以由你在lua中任意拷贝的
        */

        std::recursive_mutex mtx;
        xshare_type vars;
};

xshare_type::iterator xshare_find(lua_State *s, xshare_table *p) {
        auto it = p->vars.end();
        switch (lua_type(s, 2)) {
        case LUA_TNUMBER: {
                //在实际使用中,整数判断,并没有什么意义,但是为了符合lua本身的规则,我还是实现了
                double dv = lua_tonumber(s, 2);
                if (dv - floor(dv) < 1e-6)
                        it = p->vars.find((intptr_t)dv);
                else
                        it = p->vars.find(dv);
                break;
        }
        case LUA_TSTRING:
                it = p->vars.find(lua_tostring(s, 2));
                break;
        default:
                break;
        }
        return it;
}

int lua_xshare_get(lua_State *s) 
{
        //从lua传过来的table,只是一份用于获取数据的副本,它里面只有一个元素__ptr_,用于保存xshare_table指针
        lua_pushstring(s, "__ptr_");
        lua_gettable(s, 1);
        xshare_table *p = (xshare_table *)lua_touserdata(s, -1);
        lua_pop(s, 1);

        auto it = xshare_find(s, p);
        if (it == p->vars.end()) 
                return 0;

        switch (it->second.index()) {
        case 0://std::string
                lua_pushstring(s, std::get<0>(it->second).c_str());
                break;
        case 1://intptr_t
                lua_pushinteger(s, std::get<1>(it->second));
                break;
        case 2://double
                lua_pushnumber(s, std::get<2>(it->second));
                break;
        case 3://xshare_bool(unsigned char)
                lua_pushboolean(s, std::get<3>(it->second));
                break;
        case 4://xshare_table*
                //创建副本table,设置xshare_table指针
                lua_newtable(s);
                lua_pushstring(s, "__ptr_");
                lua_pushlightuserdata(s, std::get<4>(it->second));
                lua_settable(s, -3);

                //每一个返回到lua的副本table,都为它设置用于存取数据的元表
                lua_getglobal(s, "__xshare_object_metatable");
                lua_setmetatable(s, -2);//设置元表
                break;
        }
        return 1;
}

void xshare_set_tab(lua_State *s, xshare_table *t, int n) {

        lua_pushnil(s);
        intptr_t ikey = 1;//顺序索引
        while (lua_next(s, n)) {
                xshare_key key;
                int kt = lua_type(s, -2);
                int vt = lua_type(s, -1);
                switch (kt) {
                case LUA_TSTRING:
                        key = lua_tostring(s, -2);
                        break;
                case LUA_TNUMBER: {
                        double dv = lua_tonumber(s, 2);
                        if (dv == 0)//t = {1,2,3}这样的代码时,所有key都是0,这种时候,就从顺序索引来设置key
                                key = ikey++;
                        else
                                key = dv;
                        break;
                }
                default:
                        //莫名其妙的东西作为key时,直接报错
                        luaL_error(s, "invalid xshare key type:%s", lua_typename(s, kt));
                        break;
                }
                
                switch (vt) {
                case LUA_TNIL: {
                        auto it = t->vars.find(key);
                        if (it != t->vars.end())
                                t->vars.erase(it);
                        break;
                }
                case LUA_TTABLE:
                        xshare_set_tab(s, t, -2);
                        break;
                case LUA_TSTRING:
                        t->vars[key] = lua_tostring(s, -1);
                        break;
                case LUA_TNUMBER:{
                        double dv = lua_tonumber(s, -1);
                        if (dv - floor(dv) < 1e-6)
                                t->vars[key] = (intptr_t)dv;
                        else
                                t->vars[key] = dv;
                        break;
                }
                case LUA_TBOOLEAN:
                        t->vars[key] = (xshare_bool)lua_toboolean(s, -1);
                        break;
                default:
                        //莫名其妙的东西作为value时,直接报错
                        luaL_error(s, "invalid xshare value type:%s", lua_typename(s, vt));
                        break;
                }
                lua_pop(s, 1);
        }
}

int lua_xshare_set(lua_State *s)
{
        lua_pushstring(s, "__ptr_");
        lua_gettable(s, 1);
        xshare_table *p = (xshare_table *)lua_touserdata(s, -1);
        lua_pop(s, 1);

        auto it = p->vars.end();
        xshare_variant *xv = nullptr;

        int vt = lua_type(s, 3);
        int kt = lua_type(s, 2);
        switch (kt) {
        case LUA_TNUMBER: {
                double dv = lua_tonumber(s, 2);
                if (dv - floor(dv) < 1e-6) {
                        it = p->vars.find((intptr_t)dv);

                        if (it != p->vars.end()) {

                                //如果value是nil,就删除掉元素,直接返回
                                if (vt == LUA_TNIL) {
                                        p->vars.erase(it);
                                        return 0;
                                }
                                xv = &(it->second);
                        }
                        else
                        {
                                xv = &(p->vars[(intptr_t)dv]);
                        }
                }
                else {
                        it = p->vars.find(dv);
                        if (it != p->vars.end()) {
                                if (vt == LUA_TNIL) {
                                        p->vars.erase(it);
                                        return 0;
                                }
                                xv = &(it->second);
                        }
                        else
                        {
                                xv = &(p->vars[dv]);
                        }
                }
                break;
        }
        case LUA_TSTRING: {
                const char *pstr = lua_tostring(s, 2);
                it = p->vars.find(pstr);
                if (it != p->vars.end()) {
                        if (vt == LUA_TNIL) {
                                p->vars.erase(it);
                                return 0;
                        }
                        xv = &(it->second);
                }
                else
                {
                        xv = &(p->vars[pstr]);
                }
                break;
        }
        default:
                luaL_error(s, "invalid xshare key type:%s", lua_typename(s, kt));
                break;
        }
        
        switch (vt) {
        case LUA_TNUMBER: {
                if (xv->index() == 4)//如果之前的value是一个table,则删除指针
                        delete (std::get<4>(*xv));
                double dv = lua_tonumber(s, 3);
                if (dv - floor(dv) < 1e-6)
                        *xv = (intptr_t)dv;
                else
                        *xv = dv;
                break;
        }
        case LUA_TSTRING: 
                if (xv->index() == 4)
                        delete (std::get<4>(*xv));
                *xv = lua_tostring(s, 3);
                break;
        case LUA_TBOOLEAN:
                if (xv->index() == 4)
                        delete (std::get<4>(*xv));
                *xv = (xshare_bool)lua_toboolean(s, 3);
                break;
        case LUA_TTABLE:
                if (xv->index() != 4)//如果之前的value不是table,则创建它
                        *xv = new xshare_table;
                xshare_set_tab(s, std::get<4>(*xv), 3);
                break;
        default:
                luaL_error(s, "invalid xshare value type:%s", lua_typename(s, vt));
                break;
        }

        return 0;
}


int lua_xshare_next(lua_State *s) {

        lua_pushstring(s, "__ptr_");
        lua_gettable(s, 1);
        xshare_table *p = (xshare_table *)lua_touserdata(s, -1);
        lua_pop(s, 1);
        
        xshare_type::iterator it = p->vars.end();
        if (lua_gettop(s) > 1 && lua_type(s, 2) != LUA_TNIL) {
                int kt = lua_type(s, 2);
                switch (kt) {
                case LUA_TNUMBER: {
                        double dv = lua_tonumber(s, 2);
                        if (dv - floor(dv) < 1e-6)
                                it = p->vars.find((intptr_t)dv);
                        else
                                it = p->vars.find(dv);
                }
                case LUA_TSTRING:
                        it = p->vars.find(lua_tostring(s, 2));
                }
                ++(it);
        }
        else
                it = p->vars.begin();

        
        if (it == p->vars.end())
                return 0;

        switch (it->first.index()) {
        case 0:
                lua_pushstring(s, std::get<0>(it->first).c_str());
                break;
        case 1:
                lua_pushinteger(s, std::get<1>(it->first));
                break;
        case 2:
                lua_pushnumber(s, std::get<2>(it->first));
                break;
        }

        switch (it->second.index()) {
        case 0://std::string
                lua_pushstring(s, std::get<0>(it->second).c_str());
                break;
        case 1://intptr_t
                lua_pushinteger(s, std::get<1>(it->second));
                break;
        case 2://double
                lua_pushnumber(s, std::get<2>(it->second));
                break;
        case 3://xshare_bool(unsigned char)
                lua_pushboolean(s, std::get<3>(it->second));
                break;
        case 4://xshare_table*
                //创建副本table,设置xshare_table指针
                lua_newtable(s);
                lua_pushstring(s, "__ptr_");
                lua_pushlightuserdata(s, std::get<4>(it->second));
                lua_settable(s, -3);

                //每一个返回到lua的副本table,都为它设置用于存取数据的元表
                lua_getglobal(s, "__xshare_object_metatable");
                lua_setmetatable(s, -2);//设置元表
                break;
        }

        return 2;
}

int lua_xshare_pairs(lua_State *s)
{
        lua_pushcfunction(s, lua_xshare_next);
        lua_pushvalue(s, 1);
        lua_pushnil(s);
        return 3;
}

xshare_table xtabs;

int lua_xshare_new(lua_State *s) {
        std::lock_guard<std::recursive_mutex> lg(xtabs.mtx);
        xshare_table *_Result = nullptr;
        auto _Name = lua_tostring(s, 1);
        auto it = xtabs.vars.find(_Name);
        if (it != xtabs.vars.end())
                _Result = std::get<4>(it->second);
        else {
                _Result = new xshare_table;
                xtabs.vars[_Name] = _Result;
        }
        lua_newtable(s);
        lua_pushstring(s, "__ptr_");
        lua_pushlightuserdata(s, _Result);
        lua_settable(s, -3);
        lua_getglobal(s, "__xshare_object_metatable");
        lua_setmetatable(s, -2);
        return 1;
}

int lua_xshare_lock(lua_State *s) {
        lua_pushstring(s, "__ptr_");
        lua_gettable(s, 1);
        xshare_table *p = (xshare_table *)lua_touserdata(s, -1);
        lua_pop(s, 1);
        p->mtx.lock();
        return 0;
}

int lua_xshare_unlock(lua_State *s) {
        lua_pushstring(s, "__ptr_");
        lua_gettable(s, 1);
        xshare_table *p = (xshare_table *)lua_touserdata(s, -1);
        lua_pop(s, 1);
        p->mtx.unlock();
        return 0;
}

int lua_xshare_init(lua_State *s) {
        lua_newtable(s);
        lua_pushcfunction(s, lua_xshare_get);
        lua_setfield(s, -2, "__index");

        lua_pushcfunction(s, lua_xshare_set);
        lua_setfield(s, -2, "__newindex");

        lua_pushcfunction(s, lua_xshare_pairs);
        lua_setfield(s, -2, "__pairs");

        lua_setglobal(s, "__xshare_object_metatable");
        

        lua_newtable(s);

        lua_pushcfunction(s, lua_xshare_new);
        lua_setfield(s, -2, "new");

        lua_pushcfunction(s, lua_xshare_lock);
        lua_setfield(s, -2, "lock");

        lua_pushcfunction(s, lua_xshare_unlock);
        lua_setfield(s, -2, "unlock");

        lua_setglobal(s, "xshare");

        return 0;
}


int main(int argc, char **argv)
{

        auto t1 = std::thread([]() {
                lua_State *s1 = luaL_newstate();
                luaL_openlibs(s1);
                lua_xshare_init(s1);
                luaL_dofile(s1, "G:\\vs2017\\ConsoleApplication2\\x64\\Release\\script\\xshare.lua");
                lua_close(s1);
        });

        std::this_thread::sleep_for(std::chrono::microseconds(1));//这里的sleep,是为了让上面那个线程先跑一会儿,因为本例中对共享table的数据写入,是由它完成的。。

        auto t2 = std::thread([]() {
                lua_State *s2 = luaL_newstate();
                luaL_openlibs(s2);
                lua_xshare_init(s2);
                luaL_dofile(s2, "G:\\vs2017\\ConsoleApplication2\\x64\\Release\\script\\xshare2.lua");
                lua_close(s2);
        });

        t1.join();
        t2.join();
        
        
        
        return 0;
}

  

下面是lua代码:

--xshare.lua

local xt = xshare.new('test share table')


print('******************s1******************');
xshare.lock(xt);
xt.a = 'text';
print(xt.a);--text
xt.b = 111222333;
print(xt.b);--1122333
xt.c = true;
print(xt.c)--true
xt.c = false;
print(xt.c)--false

xt.d = {1,2,3};


--[[
    1
    2
    3
]]
for i, e in ipairs(xt.d) do
    print(e);
end

xt.d[4] = 4;

--[[
    1
    2
    3
    4
]]
for i, e in ipairs(xt.d) do
    print(e);
end



xt.e = {aa='1t', bb=2, cc=true};

--[[
    要注意:hash表遍历是不能保证顺序的

    aa  1t
    bb  2
    cc  true
]]
for i, e in pairs(xt.e) do
    print(i, e)
end

xshare.unlock(xt);

  

--xshare2.lua

local xt = xshare.new('test share table') print('******************s2******************'); xshare.lock(xt); print(xt.a);--text print(xt.b);--1122333 print(xt.c)--true --[[ 1 2 3 4 ]] for i, e in ipairs(xt.d) do print(e); end --[[ 要注意:hash表遍历是不能保证顺序的 aa 1t bb 2 cc true ]] for i, e in pairs(xt.e) do print(i, e) end xshare.unlock(xt);

 

这是输出:

******************s1******************

text

111222333

true

false

1

2

3

1

2

3

4

aa 1t

cc true

bb 2

******************s2******************

text

111222333

false

1

2

3

4

aa 1t

cc true

bb 2