skynet与lua/c编程

文章目录

  • 前言
  • 一、lua的开发
    • lua数据类型
    • lua的元表
    • 闭包
  • 二、lua/c的接口编程
    • 虚拟栈
    • C 闭包
    • 注册表
    • userdata
    • lightuserdata


前言

掌握框架的核心开发技能
skynet抓好,actor,协程和消息

一、lua的开发

脚本语言目前效率最高的
lua的数据结构只有一个table,不支持多线程
用户抽象与底层抽象
用户抽象:一个用户对应一个actor,这不一定正确
底层抽象:一个actor对应一个lua虚拟机,一个actor一个c接口的回调,一个actor一个消息队列,一个消息对应一个协程处理,一个工作线程同时只能运行一个actor
开发思路就在于怎么去抽象actor
一个skynet节点当中可以创建成千上万个actor,底层会有很多线程池。

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 类型;
lua的数组是从下标1开始的

lua的元表

lua数据结构只有table一种类型,但是可以进行扩展,可以扩展为元表,元表也是来扩展table的功能的
像下边那样,访问一个不存在的值,会返回一个nil,打印不存在怎么办,我们就可以设置一个元表

local tab = {[1]=1,[2]=2,[3]=3,}
print(tab[4])
local newtab = setmetatable(tab, {
      __index = function(t,k)
      	return "不存在"
      end,
      --如果我们不想设置成功怎么办呢,就用__newindex
      __newindex = function(t,k,v)
      	error("不能修改 t")
      end,
      --内存释放的时候,回调下边的方法
      __gc = funtion(t)
      	print("gc")
      end
})
tab[4]=10
print(tab[4])

运行效果,这是没有__index的时候
skynet与lua/c编程_第1张图片
这就是相应操作的时候,对应的回调
常用的有:
__index :索引 table[key] 。 当 table 不是表或是表 table 中不存在 key 这个键时,这个
事件被触发。 此时,会读出 table 相应的元方法。
__newindex :索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或
是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。
__gc :元表中用一个以字符串 " __gc " 为索引的域,那么就标记了这个对象需要触发终结器;
创建虚拟机的时候,自动创建协程

-- 主协程,支持闭包,所以不需要回调作为参数

local co = coroutine.create(function (arg1)
    -- 创建的协程
    -- local run, ismain = coroutine.running()
    -- print(run, ismain, arg1)
    local ret1 = arg1+1
    local arg2 = coroutine.yield(ret1)
    local ret2 = arg2+1
    return ret2
end)

local co1 = coroutine.running()
local arg1 = 1
local ok, ret1, ret2
ok, ret1 = coroutine.resume(co, arg1)
print(co1, ok, ret1)
ok, ret2 = coroutine.resume(co, ret1)
print(co1, ok, ret2)

刚刚创建的协程是一个休眠的状态,所以问你需要用coroutine.resume()去唤醒
右边是主协程运行的,将arg1这个参数传递到左边的函数中去,当运行到coroutine.yield(ret1)这个让出协程的时候,这个参数会成为我们resume的返回值
然后继续运行右边的主协程,也就是下边的打印,然后又是ret2
skynet与lua/c编程_第2张图片

闭包

闭包这个概念出现在rust,go,python,也就是函数内部可以访问函数外部的变量
函数内部可以访问函数外部的变量;
lua 文件是一个匿名函数;
lua 内部函数可以访问文件中函数体外的变量;
实现
C 函数以及绑定在 C 函数上的上值(upvalues);
闭包就是函数内部可以访问函数外部的值,我们来看看运行效果
skynet与lua/c编程_第3张图片闭包的组成,有我们的函数体本身,还有upvalue表
函数+upvalue
在lua当中
if 0 then这种语句中,0代表一个值,代表真,只有在not,nil,false才代表否
与其他语言差异
没有入口函数
索引从1开始
闭包
多返回值
函数是第一类型
尾递归,不占用栈空间
条件表达式: nil 或者 false 为假;非 nil 为真;
多元运算: A and B or C 其中 A、B、C 均为表达式;类似于 c/c++ 中的 A ? B : C ;差异在于
条件表达式的差异;
非运算符:是 ~ 而不是 ! ;所以不等于为 ~= ;

二、lua/c的接口编程

skynet、openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使用 c 与 lua 交互,这样才学会了 lua 作为胶水语言的精髓;
skynet与lua/c编程_第4张图片
skynet与lua/c编程_第5张图片

虚拟栈

栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型;
lua 调用 c 的函数都得到一个新的栈,独立于之前的栈;
c 调用 lua,每一个协程都有一个栈;
c 创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈;
无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。
LUA_MINSTACK 一般被定义为 20 , 因此,只要你不是不断的把数据压栈, 通常你不用关心堆栈大小
skynet与lua/c编程_第6张图片c导出给lua也就是lua调用c接口,这几个头文件是必须要的
skynet与lua/c编程_第7张图片
导出接口,它会把圆圈中的下划线改成点,变成uv.c
在这里插入图片描述
函数第一行是导出接口,把所有要导出的接口写在l这个数组里边,也就是23行开始的地方
skynet与lua/c编程_第8张图片
引号里边的echo是lua使用的参数,lecho是一个函数指针
skynet与lua/c编程_第9张图片

写函数接口,大括号里边都在操作虚拟栈, 取上值操作是lua_upvalueindex(1)取的上值,然后下边继续设置上值skynet与lua/c编程_第10张图片
第一个栈是1这个值,第二个栈是0这个值,上值是0这个值,作为闭包导出给这个so.echo
skynet与lua/c编程_第11张图片
lua是不能直接调用C语言的,得先把C语言编译成动态库,然后lua就可以使用它了
skynet与lua/c编程_第12张图片
编译成动态库
skynet与lua/c编程_第13张图片

C 闭包

通过 lua_pushcclosure 用来创建 C 闭包;
通过 lua_upvalueindex 伪索引来获取上值(lua 值);
可以为多个导出函数(c 导出函数给 lua 使用)共享上值,这样可以少传递一个参数;

注册表

与lua虚拟机绑定的结构
可以用来在多个 c 库中共享 lua 数据(包括 userdata 和 lightuserdata );
userdata lua是一个类型
lua分配内存,lua释放(gc)没有引用
skynet与lua/c编程_第14张图片

一张预定义的表,用来保存任何 c 代码想保存的 lua 值;
使用 LUA_REGISTRYINDEX 来索引;
例子:获取 skynet_context ;

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释放
轻量用户数据也是指向一块内存的指针,但是该内存由 c 来创建和销毁;通常这块内存的生命周期由 c 宿主语言来控制;可以将 lightuserdata 绑定在注册表中,让多个 lua 库共享该数据;在skynet 中, lightuserdata 可以指向同一块数据,在多个 Actor 中传递这个 lightuserdata ,然后分别为这个 lightuserdata 创建一个 userdata ; 在 userdata 中的 __gc 来释放这个lightuserdata ;注意:为了避免这块内存多次释放,需要为这块内存加上引用计数;同时skynet 中 actor 是多线程环境下运行,所以需要为该 lightuserdata 加上锁;这个锁必须是自旋锁或者原子操作,因为 actor 调度是自旋锁,必须使用比它更小的粒度的锁;如果lightuserdata 操作粒度过大,应该改成只在一个 actor 中加载,其他 actor 通过消息来共享数
据;

你可能感兴趣的:(笔记,lua)