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

猜你喜欢

转载自www.cnblogs.com/babypapa/p/11711389.html