为何要在多个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版本到较新的版本。
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;
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);
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;
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;
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);
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);
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虚拟机间共享table呢, 因为lua是不支持真正的多线程的,Lua中的协程其实也是在单线程中运行的。所以为了发挥cpu的最大性能,我们需要通过多线程异步执行一些任务。这些任务线程是不会阻塞lua虚拟机线程的执行。当异步线程执行完成之后就需要把数据或者消息传递给主线程lua虚拟机。 当然数据的传递有很多种方式,比较常见的就是直接传递一个c++的对象或者 以字符串char*传递一块内存。 但是由于c++对象的数据无法在lua中直接使用所以往往接收后还需要额外的解......
表是关联数组,不仅可以用数字索引,还可以用字符串索引。
表值不仅可以是整数,字符串,浮点数,还可以是表引用。
没有固定大小,您可以向表中动态添加任意数量的元素。 内存空间是动态扩展的。
低内存碎片,位于底层,内存空间分为区域,页面,平板块。 它可以选择最合适的尺寸进行分配。
高内存分配性能
内存空间在初始化阶段被占用,但不绑定物理页,因此没有内存使用。 当表使用空间时,它仅触发页面错误以将虚拟内存与物理页面绑定,因此无需系统调用即可延迟。
平板池中有某种块,页面池中有某种页面,因此为用户分配内存,只是在数组或树
数据
结构中进行搜索。
没有多余的内存占用,当可用内存达到阈值时,内存将通过从物理页面取消绑定的虚拟内存释放到系统中。
不用担心进程死亡,
一、脚本
语言
脚本
语言
通常是解释执行的,每一门脚本
语言
都会有自己定义的OpCode(Operation Code,也称为bytecode,一般翻译为“操作码”或者“字节码”),这些字节码是由脚本
语言
经过编译器前端处理之后,生成的,再将它放到这门
语言
的
虚拟机
中逐个执行。
而像C这样的都是直接编译器编译之后生成与当前硬件环境相匹配的汇编代码,不经过
虚拟机
,可以直接为机器识别
脚本
语言
这样做得好处
1、可运行平台 比较广,对接的是
虚拟机
,由
虚拟机
去处理硬件和软件平台的差异
2、由于脚本
语言
需要由
虚拟机
执行,中间
①若
Lua
虚拟机
堆栈里有N个元素,则可以用 1 ~ N 从栈底向上索引,也可以用 -1 ~ -N 从栈顶向下索引,一般后者更加常用
②堆栈的每个元素可以为任意复杂的
Lua
数据
类型,堆栈中没有元素的空位,隐含为包含一个“空”类型
数据
若有4个元素分别入栈,则:
①. 正数索引,栈底是1,然
由于
Lua
的
table
提供的是一种极其泛型化的存储结构方式,会导致编程人员并不会严谨的对某个
table
中的key进行规范化处理
我们设置一个
table
test={a={b={c=1}}}
类似这种结构比较深的
table
,假设我们要取的值不比较深,但是又不能保证a,b,都在程序正常进行中赋值。假设b是个nil,
print(test.a.b.c)
以上的操作就会报错,并且影响程序的正常执行,因此我们就需要做一个安全判断,由于
lua
并没有提供一个类似C#的一个安全访问操作符,所以我们可以通过以下方法来判
长串和短串
在讲String的
数据
结构和重要函数前,先强调一点,出于对性能和内存等方面的考虑,
lua
对String
实现
的方方面面,都把短字符串和长字符串区分开来处理了。比如短串会走一个先查询再创建的路子,而长串不查询,直接创建。我个人的理解大概是出于以下两个方面考虑:
复用度。短串复用度会比长串要高,或者说短串在全局的引用计数一般会比长串高。比如obj["id"
/////////////////////////////////////////////////////////////////
本篇文章是
Lua
设计与
实现
专栏的第三篇,主要结合了《
Lua
设计与
实现
》书中的第五章(
虚拟机
),以及
lua
5.3源码进行一些总结,由于原书中主要是基于
lua
5.1进行书写的,所以可能会有跟书中列举代码不一致的地方,不过大体上是保持一致的。
同时,本文
虚拟机
的概念和类型划分的内容主要参考了这篇blog的,里面讲的挺详细的。
虚拟机
基本概念
虚拟机
指借助软件系统对物理机器..
在这个例子中,我们使用 Corona SDK 的 `widget.newSwitch` 函数创建了一个单选框,并设置其位置、样式、标识符和初始状态。然后我们定义了一个名为 `onRadioPress` 的回调函数,该函数在单选框被按下时被调用。在这个回调函数中,我们检查单选框的状态,如果单选框被选中,就打印一条消息。
当用户点击单选框时,`onRadioPress` 函数会被调用,并将事件对象作为参数传递。我们可以通过检查 `event.target.isOn` 的值来判断单选框是否被选中。
以上代码是一个简单的示例,你可以根据自己的需求对其进行修改和扩展。