直观的讲: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
原文:https://www.cnblogs.com/babypapa/p/11711389.html