文章目录
概述
这次是skynet,需要一些lua/c相关的。写一篇博客,记录下。希望有所收获。
lua数据类型
- boolean , number , string , nil , function , table , userdata , lightuserdata , thread ;
boolean 为 true 、 false ;其中 false 可以解决 table 作为 array 时,元素为 nil 时造成
table 取长度未定义的行为; - number 为 integer 和 double 的总称;
- string 常量字符串;这样 lua 中字符串比较只需要进行地址比较就行了;
- nil 通常表示未定义或者不存在两种语义;
- function 函数;与其他语言不同的是,lua 中 function 为第一类型;注意 lua 中的匿名函
数,lua 文件可视为一个匿名函数;加载 lua 文件,可视为执行该匿名函数; - table 表;lua 中唯一的数据结构;既可以表示 hashtable 也可表示为 array;配合元表可以定制
表复杂的功能(如实现面对对象编程中的类以及相应继承的功能); - userdata 完全用户数据;指向一块内存的指针,通过为 userdata 设置元表,lua 层可以使用
该 userdata 提供的功能; userdata 为 lua 补充了数据结构,解决了 lua 数据结构单一的问
题;可以在 c 中实现复杂的数据结构,生成库继而导出给 lua 使用;注意: userdata 指向的内存
需要由 lua 创建,同时 userdata 的销毁也交由 lua gc 来自动回收; - lightuserdata 轻量用户数据;也是指向一块内存的指针,但是该内存由 c 创建,同时它的销毁
也由 c 来完成;不能为它创建元表,轻量用户数据只有类型元表;通常用于 lua 想使用 c 的结构,但是不能让 lua 来释放的结构;在游戏客户端中用的比较多; - thread 线程;lua 中的协程和虚拟机都是 thread 类型;
元表
常用的有:
- __index :索引 table[key] 。 当 table 不是表或是表 table 中不存在 key 这个键时,这个
事件被触发。 此时,会读出 table 相应的元方法。 - __newindex :索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或
是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。 - __gc :元表中用一个以字符串 " __gc " 为索引的域,那么就标记了这个对象需要触发终结器;
这些常用,是语言层次的,不区分客户端,服务器。
注意
- 只有 table 和 userdata 对象有独自的元表,其他类型只有类型元表;
- 只有 table 可以在 lua 中修改设置元表;
- userdata 只能在 c 中修改设置元表,lua 中不能修改 userdata 元表;
闭包
表现
- 函数内部可以访问函数外部的变量;
- lua 文件是一个匿名函数;
lua内部函数可以访问文件中函数体外的变量;
实现
- C 函数以及绑定在 C 函数上的上值(upvalues);
lua/c 接口编程
skynet、openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使
用 c 与 lua 交互,这样才学会了 lua 作为胶水语言的精髓;
skynet中调用层次
虚拟栈
- 栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型;
- lua 调用 c 的函数都得到一个新的栈,独立于之前的栈;
- c 调用 lua,每一个协程都有一个栈;
- c 创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈;
- 无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。
LUA_MINSTACK 一般被定义为 20 , 因此,只要你不是不断的把数据压栈, 通常你不用关心堆栈大小。
C闭包
- 通过 lua_pushcclosure 用来创建 C 闭包;
- 通过 lua_upvalueindex 伪索引来获取上值(lua 值);
- 可以为多个导出函数(c 导出函数给 lua 使用)共享上值,这样可以少传递一个参数;
注册表
可以用来在多个 c 库中共享 lua 数据(包括 userdata 和 lightuserdata );
- 一张预定义的表,用来保存任何 c 代码想保存的 lua 值;
- 使用 LUA_REGISTRYINDEX 来索引;
userdata
- userdata 是指向一块内存的指针,该内存由 lua 来创建,通过 void
*lua_newuserdatauv(lua_State *L, size_t sz, int nuvalue) 这个函数来创建;注意:这
块内存大小必须是固定的,不能动态增加,但是这块内存中的指针指向的数据可以动态增加;还有
就是 userdata 可以绑定若干个 lua 值(又称uservalue)(在 lua 5.3 中只能绑定一个 lua 值,
lua 5.4 可以绑定多个); userdata 与 uservalue 的关系是引用关系,也就是 uservalue 的生命周
期与 userdata 的生命周期一致, userdata gc 时,uservalue 也会被释放;通常这个特性可以
用来绑定一个 lua table 结构,因为 c 中没有 hash 结构,辅助 lua table 结构实现复杂的功
能;也可以用来实现延迟 gc,如果某个 userdata 希望晚点 gc,在 userdata 的 __gc 元表中
生成一个临时的 userdata ,然后将那个希望晚点 gc 的 userdata 绑定在这个临时 userdata
的 uservalue 上; - int lua_getiuservalue (lua_State *L, int idx, int n) 来获取绑定在 userdata 上的
uservalue; - int lua_setiuservalue (lua_State *L, int idx, int n) 来设置 userdata 上的
uservalue;
lightuserdata
轻量用户数据也是指向一块内存的指针,但是该内存由 c 来创建和销毁;通常这块内存的生命周期
由 c 宿主语言来控制;可以将 lightuserdata 绑定在注册表中,让多个 lua 库共享该数据;在
skynet 中, lightuserdata 可以指向同一块数据,在多个 Actor 中传递这个 lightuserdata ,
然后分别为这个 lightuserdata 创建一个 userdata ; 在 userdata 中的 __gc 来释放这个
lightuserdata ;注意:为了避免这块内存多次释放,需要为这块内存加上引用计数;同时
skynet 中 actor 是多线程环境下运行,所以需要为该 lightuserdata 加上锁;这个锁必须是自
旋锁或者原子操作,因为 actor 调度是自旋锁,必须使用比它更小的粒度的锁;如果
lightuserdata 操作粒度过大,应该改成只在一个 actor 中加载,其他 actor 通过消息来共享数
据;
小结
这一篇lua编程不同于之前的lua源码阅读,写那一篇的时候,主要是在写一些源码中的内容;这一篇,包括各种数据结构,c,以及lua的。感兴趣,都可以看看,也可以一起学习学习。OK,这篇结束。