直观的讲: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