1 闭包
Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。词法定界指:被嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了强大的编程能力。当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。简单的说闭包是一个函数加上它可以正确访问的 upvalues
2 元表
Lua 中的每个值都可以用一个 metatable。这个 metatable 就是一个原始的 Lua table ,它用来定义原始值在特定操作下的行为。你可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值的指定操作之行为
metatable 中的键名为 事件 (event) ,把其中的值叫作 元方法 (metamethod)
可通过函数getmetatable查询任何值的元表。函数setmetatable替换表的元表(debug库不可以)
__index:我们访问一个表中的元素不存在时,则会触发去寻找__index元方法,如果不存在,则返回nil,如果存在,则返回结果。
__newindex:当给你的表中不存在的值进行赋值时,lua解释器则会寻找__newindex元方法,发现存在该方法,则执行该方法进行赋值,注意,是使用rawset来进行赋值。
rawget (table, index)
功能:获取表中指定索引的值,此函数不会调用任何元表的方法,成功返回相应的值,当索引不存在时返回nil
注:本函数只能用于以数字索引访问的表 如:t={"1","cash"}
rawset (table, index, value) 功能:设置表中指定索引的值,此函数不会调用任何元表的方法,此函数将返回table
3 协程
协程采用的主动让出控制权的方式,因而协程中的的指令顺序是可以预计的;相反线程由于采用的是抢占式,导致线程的执行顺序不可知,因此存在同步的问题
Lua语言实现的协程是一种非对称式(asymmetric)协程,或称半对称式(semi-symmetric)协程,又或干脆就叫半协程(semi-coroutine)。这种协程机制之所以被称为非对称的,是因为它提供了两种传递程序控制权的操作:一种是(重)调用协程(通过coroutine.resume);另一种是挂起协程并将程序控制权返回给协程的调用者(通过coroutine.yield)。
Lua 协程有三个状态:挂起态(suspended)、运行态(running)、停止态(dead)。可以通过coroutine.status来查看协程状态
创建一个协程需要调用coroutine.create 。它只接收单个参数,这个参数是 coroutine 的主函数。 create 函数仅仅创建一个新的coroutine 然后返回一个类型为thread的对象,并不会启动 coroutine 的运行。
lua所支持的协程全称被称作协同式多线程(collaborative multithreading)。Lua为每个coroutine提供一个独立的运行线路。然而和多线程不同的地方就是,coroutine只有在显式调用yield函数后才被挂起,同一时间内只有一个协程正在运行。
4 模块
Lua提供简易的加载及创建模块的方法,由require、module方法及package表组成
1、module (name [, ···])
功能:建立一个模块。
当package.loaded[name]中存在时,当中的表作为module;
当在全局表中存在name指定的表时,此表作为module;
当以前两种情况都不存表name时,将新建一个表,并使其作为全局名name的值,并package.loaded[name],而且设t._NAME为name,t._M为module,t._PACKAGE为包的全名(模块名-组件a.b.c);最后把此module设t作为当前函数的新环境表和package.loaded[name]的新值(也就是说,旧的环境表将不能访问,除了加上package.seeall参数外),以被require使用
module(name)后的可选参数为接收module名的函数,如package.seeall
2、require (modname)
功能:加载指定的模块。
此函数先检测package.loaded表中是否存在modname,存在则直接返回当中的值,没有则通过自定义的加载器加载modname。
查找加载器顺序:
(1)检测package.preload表是否存在modname,有则加载
(2)通过Lua Loader加载,通过查找存放于package.path的路径加载,有则加载
(3)通过C Loader加载,通过查找存放于package.cpath的路径加载,有则加载
(4)通过all-in-one Loader加载:
通过查找modname.dll并查找当中的luaopen_
其中XXXX为载块名-后的字符用_替换.后的字符:如:a.v1-b.c 当函数名为luaopen_b_c
当require查找的不是一个Lua库或C库,它就会调用all-in-one loader,此加载器是用C路径作为载块的目录,
当查找到合适的加载器时,require就会加载其中的模块,当加载器有返回值,将会存放于package.loaded[modname]表。最后返回package.loaded[modname]表
当加载失败时,require将触发错误
require只认文件名,不认路径名。要加入路径名信息的话,就要写成父模块子模块的形式。
3、package.cpath
功能:用于require C loader的搜索路径
可以通过修改LUA_CPATH变量(luaconf.h)修改此值
4、package.loaded
功能:一个用于让require知道哪些模块已加载的记录表,如果package.loaded已经有require要的值,则直接返回此值
5、package.loadlib (libname, funcname)
功能:通过动态连接C函数库方式加载Lua扩展库
libname为库文件名,funcname为入口函数(此函数必须为纯C接口函数 c++则需用 extern "C" {} 进行限制)
6、package.path
功能:用于require Lua loader的搜索路径
可以通过修改LUA_PATH变量(luaconf.h)修改此值
7、package.preload
功能:一个用于保存特殊模块加载器的表
8、package.seeall(module)
功能:为module设置一个元表,此元表的__index字段的值为全局环境_G。所以module可以访问全局环境
Lua5.1.4代码分析 -如何实现Lua代码的热更新
2013-07-08 11:00 能很好的支持代码热更新机制,是大部分选择要嵌入脚本语言的原因之一。好处很简单,脚本代码可以热更新的话,调试和线上解决问题都可以不用重启程序了,对开发效率有很大的帮助。今天就来谈谈Lua代码如何实现热更新。
先简单回顾之前提过的模块和require机制。Lua内部提供了一个require函数,来实现模块的加载,它做的事情主要是以下几个:
在registry["_LOADED"]表中判断该模块是否已经加载过了,如果是则返回,避免重复加载某个模块代码。
依次调用注册的loader来加载模块 将加载过的模块赋值给registry["_LOADED"]表。 而如果要实现Lua的代码热更新,其实也就是需要重新加载某个模块,因此就要想办法让Lua认为它之前没有加载过。查看Lua代码发现,registry["_LOADED"]表,实际上对应的是package.loaded表,这在以下函数中有体现:627 LUALIB_API int luaopen_package (lua_State *L) {
/.... 655 /* set field `loaded' */ 656 luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2); 657 lua_setfield(L, -2, "loaded"); 因此事情就很简单了,需要提供一个require_ex函数,可以理解为require的增强版,使用这个函数可以动态更新某个模块的代码,这个函数可以在我的开源项目qnode的script/util.lua中找到:function require_ex( _mname )
qlog( string.format("require_ex = %s", _mname) ) if package.loaded[_mname] then qlog( string.format("require_ex module[%s] reload", _mname)) end package.loaded[_mname] = nil require( _mname ) end 这个函数做的事情一目了然。首先判断是否曾经加载过这个模块,如果有则打印一条日志表示需要重新加载某个模块,然后将该模块原来在表中注册的值赋空,然后再次调用require进行模块的加载和注册。以上了解了Lua代码热更新的原理,但是还有一些细节需要提醒一下。
第一点,如何组织你的项目中的Lua代码?我在qnode中使用的方式是,单独使用一个叫main.lua的文件调用require_ex函数来加载需要用到的lua模块,而lua虚拟机创建之后执行的是这个文件,这样的话,当你需要热更新项目中的lua代码时,只需要重新执行这 个main.lua就行了。如何通知热更新代码呢?我在qnode中使用的信号机制,当服务器收到USR1信号时,通知所有工作进程,由工作进程来重新对main.lua进行重新加载,这样就完成了lua代码的热更新,为此我写了一个简单的脚本reload.sh,就是根据当前qnode的服务器进程ID来对其发送USR1信号量的。
第二点,一般热更新的都是函数的实现,所以需要对全局变量做一些保护。比如当前某全局变量为100,表示某个操作已经进行了100次,它不能因为热更新重新置0,所以要对这些不能改变的全局变量做一个保护,最简单的方式就是这样:
a = a or 0 很单的原理,只有当前a这个变量没有初始值的时候才会赋值为0,而后面不管这个Lua文件被加载多少次,a都不会因为重新加载了Lua代码发生改变了。