LUA学习

协程

(1)协程可以颠倒调用者和被调用者的关系,且这种灵活性解决了"谁拥有主循环"的问题。这正是对诸如事件驱动编程、通过构造器构建迭代器和协作式多线程等几个看上去并不相关的问题的泛化,而协程以简单和高效的方式解决了这些问题。

(2)从多线程的角度看,协程与线程类似:协程是一系列的可执行语句,拥有自己的栈、局部变量和指令指针,同时协程又与其他协程共享了全局变量和其他几乎一切资源。 线程与协程的主要区别在于,一个多线程程序可以并行运行多个线程,而协程却需要彼此协作地运行,即在任意指定的时刻只能有一个协程运行,且只有当正在运行的协程显式地要求被挂起时其执行才会暂停。

协程基础

(1)Lua语言中协程相关的所有函数都被放在表coroutine 函数中。函数create 用于创建新协程,该函数只有一个参数,即协程要执行的代码的函数(协程体)函数create返回一个"thread"类型的值,即新协程。通常,函数create的参数是一个匿名函数。

(2)一个协程有以下四种状态,即挂起、运行、正常和死亡。我们可以通过函数coroutine.status来检查协程的状态。

(3)当一个协程被创建时,它处于挂起状态,即协程不会在被创建时自动运行。函数coroutine.resume用于启动或再次启动一个协程的执行,并将其状态由挂起改为运行。

(4)协程的真正强大之处在于函数yield ,该函数可以让一个运行中的协程挂起自己,然后在后续恢复运行。当唤醒协程后,它就会开始执行直到遇到第一个yield,然后进入挂起状态。我们唤醒协程时函数yield才会最终返回,**从当前yield行的下一行开始执行,**然后协程会继续执行直到遇到下一个yield或执行结束。

(5)当协程A唤醒协程B时,协程A既不是挂起状态(因为不能唤协程A),也不是运行状态(因为正在运行的协程是B)。所以,协程A此时的状态就被称为正常状态。

(6)Lua 语言中一个非常有用的机制是通过一对resume-yield来交换数据。第一个 resume函数(没有对应等待它的yield)会把所有的额外参数传递给协程的主函数。在函数 coroutine. resume的返回值中,第一个返回值为true 表示没有错误,之后的返回值对应函数 yield 的参数。 与之对应的是,函数 coroutine.yield 的返回值是对应的resume的参数。当一个协程运行结束时,主函数所返回的值都将变成对应函数resume的返回值。注意,resume获得返回值时,yield对应的语句被挂起,要在被resume之后再执行完毕。

(7)Lua语言提供的是所谓的非对称协程,也就是说需要两个函数来控制协程的执行,一个用于挂起协程的执行,另一个用于恢复执行。而其他一些语言提供的是对称协程,只提供一个函数用于在一个协程和另一个协程之间切换控制权。术语半协程表示协程的一种受限制版实现。在这种实现中,一个协程只能在它没有调用其他函数时才可以挂起,即在调用找中没有挂起的调用时才可以挂起。只有半协程的主函数才能让出执行权。

哪个协程占据主循环

(1)一个经典示例是生产者-消费者问题。在生产者-消费者问题中涉及两个函数,一个函数不断地产生值,另一个函数不断地消费这些值。生产者和消费者都处于活跃状态,它们各自具有自己的主循环,并且都将对方视为一个可调用的服务。由于成对的resume-yield可以颠倒调用者与被调用者之间的关系,因此协程提供了一种无须修改生产者和消费者的代码结构就能匹配它们执行顺序的理想工具。

(2)当一个协程调用函数yield时,它不是进入了一个新函数,而是返回一个挂起的调用(调用的是函数resume)。同样 ,对函数resume的调用也不会启动一个新函数,而是返回一个对函数yield的调用。

Lua 复制代码
local co = coroutine.create(function (arg1)
    -- 创建的协程
    -- local run, ismain = coroutine.running()
    -- print(run, ismain, arg1)
    local ret1 = arg1+1
    local arg2 = coroutine.yield(ret1)
    print(arg2, 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("1:",co1, ok, ret1)
ok, ret2 = coroutine.resume(co, ret1)
print("2:",co1, ok, ret2)

Lua虚拟栈

栈中只能存放Lua类型的值,如果想保存C类型的值,需要先将C类型转换为Lua类型;

Lua每次调用C函数都会生成新的栈,独立于之前的栈(不是全局结构),每个函数都有私有的局部栈 ,lua调用c函数,第一个参数总是位于 这个局部栈中索引为1 的位置

C在创建虚拟机是,伴随创建一个主协程和一个虚拟栈

无论何时Lua再调用C时,只保证至少有LUA_MINSTACK大小的堆栈空间可以使用。LUA_MINSTACK一般定义为20,只要你不是一致压栈,通常不用担心堆栈大小。

Lua中的栈有两排索引,正数1索引的位置在栈底,负数索引-1在栈顶,这样做的好处是不需要知道栈的大小,只需要查找正负索引1的位置就能确定栈顶和栈底的位置。

注册表

注册表(registry)是一张只能被C代码访问的全局表。通常情况下,我们使用注册表来存储多个模块间共享的数据。

注册表总是位于伪索引(pedo-ndex)LUA_REGISTRYINDEX中。伪索引就像一个栈中的索引,但它所关联的值不在栈中。

注册表的使用场景
  • 可以用来在多个c库共享lua数据
  • 是一张 预定义 的表,只能被c代码访问的全局表,用来保存任何c代码想保存的lua值
  • 使用 LUA_REGISTRYINDEX 来索引
Lua源码分析 - 主流程篇 - 注册表的实现
代码示例
cpp 复制代码
//lua-reg1.c
#include <lua.h>

#include <lauxlib.h>

#include <lualib.h>

#include <stdio.h>

static int
lecho (lua_State *L) {
    lua_getfield(L, LUA_REGISTRYINDEX, "mk.reg.c"); 
    //描述: 将t[k]元素push到栈顶. 其中t是index处的table.这个函数可能触发index元方法.
    // 此时栈顶放了一张表如下
    //        {["mk.reg.c"] = {1000, 2000}}
    lua_rawgeti(L, -1, 1);
    // -1 是读取栈顶的表  1 是指读这个表中索引为1 的field
    // 相当于 取出 1000 放在栈顶
    // 此时 栈上 1 的位置 为 table   2的位置为 1000
    lua_Integer n = lua_tointeger(L, -1);
    // 将栈顶的值转化为整数
    n++;
    lua_pop(L, 1);
    // 将栈顶的值 pop 出
    // 此时栈上只有一个 table
    lua_pushinteger(L, n);
    // 将运算后的 n push 到栈上
    // 此时栈上 1 的位置为 table   2的位置为 1001
    lua_rawseti(L, -2, 1);
    // 将 栈顶的值 设置到 1 位置中table 索引为 1的位置
    // 此时 table = {1001,2000}
    // 此时栈上只有一个 table 因为设置的时候 把栈顶的值丢掉了
    const char* str = lua_tostring(L, 1);
    fprintf(stdout, "reg1[n=%lld]----%s\n", n, str);
    return 0;
}

static const luaL_Reg l[] = {
	{"echo", lecho},
	{NULL, NULL},
};

int
luaopen_reg1_c(lua_State *L) { 
    if (lua_getfield(L, LUA_REGISTRYINDEX, "mk.reg.c") == LUA_TNIL) {
        lua_createtable(L, 2, 0); //1
        lua_pushinteger(L, 1000); //2

        lua_rawseti(L, -2, 1); //1   {1000}

        lua_pushinteger(L, 2000);//2
        lua_rawseti(L, -2, 2);//  索引为-2的位置的值 {1000,2000}
        int topindex = lua_gettop(L);
	    printf("栈顶元素索引:%d\n", topindex);

        lua_setfield(L, LUA_REGISTRYINDEX, "mk.reg.c");
        topindex = lua_gettop(L);
	    printf("栈顶元素索引:%d\n", topindex);
        fprintf(stdout, "luaopen_reg1111_c 注册表 mk.reg.c\n");
    }
    luaL_newlib(L, l);
	return 1;
}
cpp 复制代码
//lua-reg2.c
#include <lua.h>

#include <lauxlib.h>

#include <lualib.h>

#include <stdio.h>

static int
lecho (lua_State *L) {
    lua_getfield(L, LUA_REGISTRYINDEX, "mk.reg.c");
    lua_rawgeti(L, -1, 2);
    lua_Integer n = lua_tointeger(L, -1);
    n++;
    lua_pop(L, 1);
    lua_pushinteger(L, n);
    lua_rawseti(L, -2, 2);
    const char* str = lua_tostring(L, 1);
    fprintf(stdout, "reg2[n=%lld]----%s\n", n, str);
    return 0;
}

static const luaL_Reg l[] = {
	{"echo", lecho},
	{NULL, NULL},
};

int
luaopen_reg2_c(lua_State *L) { 

    if (lua_getfield(L, LUA_REGISTRYINDEX, "mk.reg.c") == LUA_TNIL) {
        lua_createtable(L, 2, 0);
        lua_pushinteger(L, 1000);
        lua_rawseti(L, -2, 1);
        lua_pushinteger(L, 2000);
        lua_rawseti(L, -2, 2);
        lua_setfield(L, LUA_REGISTRYINDEX, "mk.reg.c");
        fprintf(stdout, "luaopen_reg2222_c 注册表 mk.reg.c\n");
    }
    luaL_newlib(L, l);
	return 1;
}
Lua 复制代码
package.cpath = "luaclib/?.so"


local so1 = require "reg1.c"
local so2 = require "reg2.c"


so1.echo("hello world")
so2.echo("hello world")

so1.echo("hello world")
so2.echo("hello world")

so1.echo("hello world")
--so2.echo("hello world")

so1.echo("hello world")
--so2.echo("hello world")

so1.echo("hello world")
--so2.echo("hello world")

so1.echo("hello world")
--so2.echo("hello world")
编译命令
cc -g -O2 -Wall -I./lua/src -fPIC --shared lualib-src/lua-reg1.c -o luaclib/reg1.so
cc -g -O2 -Wall -I./lua/src -fPIC --shared lualib-src/lua-reg2.c -o luaclib/reg2.so

常用API

lua_getfield

原型: void lua_getfield (lua_State *L, int index, const char *k);

描述: 将t[k]元素push到栈顶. 其中t是index处的table.

这个函数可能触发index元方法.

lua_setfield

原型: void lua_setfield (lua_State *L, int index, const char *k);

描述: 为table中的key赋值. t[k] = v . 其中t是index处的table , v为栈顶元素.

这个函数可能触发newindex元方法.

调用完成后弹出栈顶元素(value).

lua_rawgeti##

原型:void lua_rawgeti (lua_State *L, int index, int n);

解释:把 t[n] 的值压栈, 这里的 t 是指给定索引 index 处的一个值。 这是一个直接访问,它不会触发元方法。

lua_rawseti##

原型:void lua_rawseti (lua_State *L, int index, int n);

解释: 等价于 t[n] = v, 这里的 t 是指给定索引 index 处的一个值, 而 v 是栈顶的值。函数将把这个值弹出栈。 赋值操作是直接的,不会触发元方法。

Lua 的调试

Lua 调试(Debug) - Lua 教程 | BootWiki.com

1 自省(Introspective)

在debug库中主要的自省函数是debug.getinfo。他的第一个参数可以是一个函数或者栈级别。对于函数foo调用debug.getinfo(foo),将返回关于这个函数信息的一个表。

Lua 复制代码
function myfunction ()
    print(debug.traceback("Stack trace begin------"))
    print(debug.getinfo(1))
    print("Stack trace end--------")
    end
    myfunction ()
    print(debug.getinfo(1))

1.1访问局部变量

调用debug库的getlocal函数可以访问任何活动状态的局部变量。这个函数由两个参数:将要查询的函数的栈级别和变量的索引。函数有两个返回值:变量名和变量当前值。如果指定的变量的索引大于活动变量个数,getlocal返回nil。如果指定的栈级别无效,函数会抛出错误。

在getlocal被调用的那一点,c(左侧截图)已经超出了范围,name和value都不在范围内。(记住:局部变量仅仅在他们被初始化之后才可见)也可以使用debug.setlocal修改一个局部变量的值,他的前两个参数是栈级别和变量索引,第三个参数是变量的新值。这个函数返回一个变量名或者nil(如果变量索引超出范围)

Lua 复制代码
-----------------lua脚本------------
local a=0
local b=0
 
print("主函数局部变量debug前")
print('a',a)
print('b',b)
 
function fun()
  local i=0
  local j=0
 
  print("\n函数局部变量debug前")
  print("debug.getlocal(1,1)", debug.getlocal(1, 1))
  print("debug.getlocal(1,2)", debug.getlocal(1, 2))
  
  debug.setlocal(1,1,1)    --设置1层(fun函数)的局部变量,index=1的局部变量值为4
  debug.setlocal(1,2,2)    --设置1层(fun函数)的局部变量,index=1的局部变量值为4
  debug.setlocal(2,1,3)    --设置2层(调用fun函数的main函数)的局部变量,index=1的局部变量值为4
  debug.setlocal(2,2,4)    --设置2层调用fun函数的main函数)的局部变量,index=2的局部变量值为4
 
  print("\n函数局部变量debug后:")
  print("debug.getlocal(1,1)", debug.getlocal(1,1))
  print("debug.getlocal(1,2)", debug.getlocal(1,2))
end
 
print()

fun()
 
print("\n主函数局部变量debug后")
print("a",a)
print("b",b)

----------执行结果-----------
主函数局部变量debug前
a       0
b       0


函数局部变量debug前
debug.getlocal(1,1)     i       0
debug.getlocal(1,2)     j       0

函数局部变量debug后:
debug.getlocal(1,1)     i       1
debug.getlocal(1,2)     j       2

主函数局部变量debug后
a       3
b       4

1.2访问Upvalues

我们也可以通过debug库的getupvalue函数访问Lua函数的upvalues。和局部变量不同的是,即使函数不在活动状态他依然有upvalues(这也就是闭包的意义所在)。所以,getupvalue的第一个参数不是栈级别而是一个函数(精确的说应该是一个闭包),第二个参数是upvalue的索引。Lua按照upvalue在一个函数中被引用(refer)的顺序依次编号,因为一个函数不能有两个相同名字的upvalues,所以这个顺序和upvalue并没什么关联(relevant)。

可以使用函数ebug.setupvalue修改upvalues。也许你已经猜到,他有三个参数:一个闭包,一个upvalues索引和一个新的upvalue值。和setlocal类似,这个函数返回upvalue的名字,或者nil(如果upvalue索引超出索引范围)。

Lua 复制代码
local a=0
local b=0
 
function fun1()
  a=a+1
  b=b+1
end
 
print("调用setupvalue前")
print(debug.getupvalue(fun1,1))
print(debug.getupvalue(fun1,2))
 
debug.setupvalue(fun1,1,10)
debug.setupvalue(fun1,2,20)
 
print("\n调用setupvalue后")
print(debug.getupvalue(fun1,1))
print(debug.getupvalue(fun1,2))
fun1()
print("\n func后")
print(debug.getupvalue(fun1,0))
print(debug.getupvalue(fun1,1))
print(debug.getupvalue(fun1,2)) 

--------------执行结果----------------

调用setupvalue前
a       0
b       0

调用setupvalue后
a       10
b       20

 func后

a       11
b       21
Lua 复制代码
co = coroutine.create(
       function()
         local i=0
         print(debug.getlocal(co,1,1))
         error("错误函数")
       end
)
 
coroutine.resume(co)
print(debug.traceback(co))

i       0
stack traceback:
        [C]: in function 'error'
        test-lua.lua:85: in function <test-lua.lua:82>

1.4钩子

gethook([thread]):获取钩子函数

sethook([thread,] hook, mask, count):设置钩子函数

* mask、count值设置

* c:每次调用函数的时候触发

* r:每次返回的时候触发

* l:每次执行新的一行时触发

Moreover, with a count different from zero, the hook is called also

after every count instructions.

* count大于0时,每执行count次指令时触发

When called without arguments, debug.sethook turns off the hook.

* sethook不携带参数时,会关闭钩子函数

When the hook is called, its first parameter is a string describing

the event that has triggered its call: "call", "tail call", "return",

"line", and "count". For line events, the hook also gets the new line

number as its second parameter. Inside a hook, you can call getinfo

with level 2 to get more information about the running function.

(Level 0 is the getinfo function, and level 1 is the hook function.)

* 当钩子函数触发时,第一个参数是描述触发钩子函数的事件(call、return、line、count)

* line事件还有第2个参数:新的行号

相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习