lua gc

本文介绍Lua中的自动内存管理和垃圾回收机制。Lua通过增量标记清除的垃圾收集器来管理内存,控制周期由garbage-collectorpause和garbage-collectorstepmultiplier调节。此外,还介绍了如何在C中设置userdata的垃圾收集元方法。


Lua 提供了一个自动的内存管理。这就是说你不需要关心创建新对象的分配内存操作,也不需要在这些对象不再需要时的主动释放内存。 Lua 通过运行一个垃圾收集器来自动管理内存,以此一遍又一遍的回收死掉的对象(这是指 Lua 中不再访问的到的对象)占用的内存。 Lua 中所有对象都被自动管理,包括: table, userdata、 函数、线程、和字符串。

Lua 实现了一个增量标记清除的收集器。它用两个数字来控制垃圾收集周期: garbage-collector pause 和 garbage-collector step multiplier 。
garbage-collector pause 控制了收集器在开始一个新的收集周期之前要等待多久。随着数字的增大就导致收集器工作工作的不那么主动。小于 1 的值意味着收集器在新的周期开始时不再等待。当值为 2 的时候意味着在总使用内存数量达到原来的两倍时再开启新的周期。
step multiplier 控制了收集器相对内存分配的速度。更大的数字将导致收集器工作的更主动的同时,也使每步收集的尺寸增加。小于 1 的值会使收集器工作的非常慢,可能导致收集器永远都结束不了当前周期。缺省值为 2 ,这意味着收集器将以内存分配器的两倍速运行。
你可以通过在 C 中调用 lua_gc 或是在 Lua 中调用 collectgarbage 来改变这些数字。两者都接受百分比数值(因此传入参数 100 意味着实际值 1 )。通过这些函数,你也可以直接控制收集器(例如,停止或是重启)。

使用 C API ,你可以给 userdata 设置一个垃圾收集的元方法。这个元方法也被称为结束子。结束子允许你用额外的资源管理器和 Lua 的内存管理器协同工作(比如关闭文件、网络连接、或是数据库连接,也可以说释放你自己的内存)。
一个 userdata 可被回收,若它的 Metatable 中有 __gc 这个域 ,垃圾收集器就不立即收回它。取而代之的是,Lua 把它们放到一个列表中。最收集结束后,Lua 针对列表中的每个 userdata 执行了下面这个函数的等价操作:
     function gc_event (udata)
       local h = metatable(udata).__gc
       if h then
         h(udata)
       end
     end
 
在每个垃圾收集周期的结尾,每个在当前周期被收集起来的 userdata 的结束子会以它们构造时的逆序依次调用。也就是说,收集列表中,最后一个在程序中被创建的 userdata 的结束子会被第一个调用。

垃圾回收器是用来管理应用程序的内存分配和释放的。在垃圾回收器出现以前,程序员在使用内存时需要向系统申请内存空间。有些语言,例如Visual Basic,可以自动完成向系统申请内存空间的工作。但是在诸如Visual C++的语言中要求程序员在程序代码中申请内存空间。如果程序员在使用了内存之后忘了释放内存,则会引起内存泄漏。但是有了垃圾回收器,程序员就不必关心内存中对象在离开生存期后是否被释放的问题。当一个应用程序在运行的时候,垃圾回收器设置了一个托管堆。托管堆和C语言中的堆向类似,但是程序员不需要从托管堆中释放对象,并且在托管堆中对象的存放是连续的。

每次当开发人员使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。新创建的对象被放在上次创建的对象之后。垃圾回收器保存了一个指针,该指针总是指向托管堆中最后一个对象之后的内存空间。当新的对象被产生时,运行库就知道应该将新的对象放在内存的什么地方。同时开发人员应该将相同类型的对象放在一起。例如当开发人员希望向数据库写入数据的时侯,首先需要创建一个连接对象,然后是Command对象,最后是DataSet对象。如果这些对象放在托管堆相邻的区域内,存取它们就非常快。 当垃圾回收器的指针指向托管堆以外的内存空间时,就需要回收内存中的垃圾了。在这个过程中,垃圾回收器首先假设在托管堆中所有的对象都需要被回收。然后它在托管堆中寻找被根对象引用的对象(根对象就是全局,静态或处于活动中的局部变量以及寄存器指向的对象),找到后将它们加入一个有效对象的列表中,并在已经搜索过的对象中寻找是否有对象被新加入的有效对象引用。直到垃圾回收器检查完所有的对象后,就有一份根对象和根对象直接或间接引用了的对象的列表,而其它没有在表中的对象就被从内存中回收。
ECS_s32 ecs_rpt_lua(shr_ecs_lua_key_t *elk, ECS_s8 *pout, ECS_s32 outlen) { ECS_s32 rv = ECS_OK; lua_State *L = NULL; const char *errstr; long long start_time = 0; /* init state when call it first to save memory */ if (rpt_lua.state == ECS_LUA_INIT) { ecs_lua_state_init(&rpt_lua); if (!rpt_lua.L) { ECS_RPT_ERR("Init ecs lua state failed\n"); return ECS_ERROR; } } pthread_mutex_lock(&rpt_lua.lock); L = rpt_lua.L; lua_getglobal(L, rpt_lua.function); /* get function and push it to stack */ lua_pushstring(L, elk->modname); /* push modname to stack */ lua_pushstring(L, elk->subname); /* push subname to stack */ /* lua stackdump */ ecs_lua_stackdump(&rpt_lua); start_time = ecs_getRunTimeMs(); ECS_s32 ret = lua_pcall(L, 2, 1, 0); ECS_RPT_INFO("lua_pcall [%s][%s] done, use %.3fs.\r\n", elk->modname,elk->subname, (double)(ecs_getRunTimeMs() - start_time) / 1000); switch(ret) /* after lua_pcall, input arguments will be poped out */ { case LUA_ERRMEM: case LUA_ERRRUN: errstr = luaL_checkstring(L, -1); if (!errstr) errstr = "(unknown error)"; else lua_pop(L, 1); /* pop returned values out */ ECS_RPT_ERR("keyname [%s] modname[%s] subname[%s] call function:%s failed, errstr:%s.", elk->keyname, elk->modname, elk->subname, rpt_lua.function, errstr); rv = ECS_ERROR; goto leave; } /* lua stackdump */ ecs_lua_stackdump(&rpt_lua); const char *response = lua_tostring(L, -1); /* get result from top of stack */ if (strlen(response) > outlen) { lua_pop(L, 1); /* need to pop out return values */ ECS_RPT_ERR("response len[%d] is bigger than outlen[%d].", strlen(response), outlen); rv = ECS_ERROR; goto leave; } memcpy(pout, response, strlen(response)); lua_pop(L, 1); /* pop returned values out */ ECS_RPT_INFO("LUA GC COUNT1:%d KBytes.", lua_getgccount(rpt_lua.L)); lua_gc(rpt_lua.L, LUA_GCCOLLECT, 0); /* do garbage collector after calling */ ECS_RPT_INFO("LUA GC COUNT2:%d KBytes.", lua_getgccount(rpt_lua.L)); leave: pthread_mutex_unlock(&rpt_lua.lock); return rv; } 这段代码做了什么?
最新发布
11-27
这段代码是一个 C 函数 `ecs_rpt_lua`,它使用 **Lua 脚本引擎** 来动态调用一个 Lua 函数,并将执行结果返回到输出缓冲区中。这个函数主要用于嵌入式系统或服务端模块中,实现基于 Lua 的可配置逻辑处理(例如日志处理、规则引擎、报告生成等)。下面我将逐段分析其功能并解释每部分的作用。 --- ### ✅ 功能总览 该函数的作用是: > 在多线程环境下安全地调用一个全局的 Lua 函数,传入两个字符串参数(`modname` 和 `subname`),获取其返回值字符串,复制到输出缓冲区 `pout` 中。 常用于:通过 Lua 实现灵活的业务逻辑脚本,C 代码负责调度和通信。 --- ### 🔍 代码详解 ```c ECS_s32 ecs_rpt_lua(shr_ecs_lua_key_t *elk, ECS_s8 *pout, ECS_s32 outlen) { ECS_s32 rv = ECS_OK; lua_State *L = NULL; const char *errstr; long long start_time = 0; /* init state when call it first to save memory */ if (rpt_lua.state == ECS_LUA_INIT) { ecs_lua_state_init(&rpt_lua); if (!rpt_lua.L) { ECS_RPT_ERR("Init ecs lua state failed\n"); return ECS_ERROR; } } ``` #### 📌 初始化 Lua 状态机(懒加载) - 检查是否首次调用。 - 若是,则初始化 Lua 解释器环境(通过 `ecs_lua_state_init()`)。 - `rpt_lua` 是一个全局结构体,保存了共享的 Lua 状态(`lua_State* L`)、函数名 `function`、锁等。 - 使用“惰性初始化”节省资源 —— 只有第一次才创建 Lua 虚拟机。 --- ```c pthread_mutex_lock(&rpt_lua.lock); L = rpt_lua.L; lua_getglobal(L, rpt_lua.function); /* get function and push it to stack */ lua_pushstring(L, elk->modname); /* push modname to stack */ lua_pushstring(L, elk->subname); /* push subname to stack */ /* lua stackdump */ ecs_lua_stackdump(&rpt_lua); ``` #### 📌 构造 Lua 栈并准备调用 1. 加锁防止多线程冲突(因为共用同一个 `lua_State`)。 2. 获取要调用的 Lua 全局函数名(如 `"process_report"`),压入栈顶。 3. 接着压入两个参数: - `elk->modname` - `elk->subname` 4. 此时栈上从底到顶为:`function, modname, subname` 5. `ecs_lua_stackdump()` 是调试工具,打印当前栈内容以便排查问题。 --- ```c start_time = ecs_getRunTimeMs(); ECS_s32 ret = lua_pcall(L, 2, 1, 0); ECS_RPT_INFO("lua_pcall [%s][%s] done, use %.3fs.\r\n", elk->modname,elk->subname, (double)(ecs_getRunTimeMs() - start_time) / 1000); ``` #### 📌 执行 Lua 函数 - `lua_pcall(L, nargs=2, nresults=1, errfunc=0)` - 调用栈顶的函数,传入 2 个参数。 - 期望返回 1 个值。 - 不设置错误处理函数(`0` 表示无保护模式下的错误处理函数)。 - 记录执行耗时(毫秒级),用于性能监控。 --- ```c switch(ret) { case LUA_ERRMEM: case LUA_ERRRUN: errstr = luaL_checkstring(L, -1); if (!errstr) errstr = "(unknown error)"; else lua_pop(L, 1); /* pop returned values out */ ECS_RPT_ERR("keyname [%s] modname[%s] subname[%s] call function:%s failed, errstr:%s.", elk->keyname, elk->modname, elk->subname, rpt_lua.function, errstr); rv = ECS_ERROR; goto leave; } ``` #### 📌 错误处理 - 如果 `lua_pcall` 返回错误: - `LUA_ERRRUN`: 运行时错误(比如语法错、除零等) - `LUA_ERRMEM`: 内存分配失败 - 从栈顶取出错误信息字符串。 - 记录日志并设置返回状态为 `ECS_ERROR`。 - 使用 `goto leave` 统一释放资源。 > 注意:这里只检查运行时和内存错误,不包括语法错误(但若脚本已加载完成,通常不会出现)。 --- ```c /* lua stackdump */ ecs_lua_stackdump(&rpt_lua); const char *response = lua_tostring(L, -1); /* get result from top of stack */ if (strlen(response) > outlen) { lua_pop(L, 1); /* need to pop out return values */ ECS_RPT_ERR("response len[%d] is bigger than outlen[%d].", strlen(response), outlen); rv = ECS_ERROR; goto leave; } memcpy(pout, response, strlen(response)); lua_pop(L, 1); /* pop returned values out */ ``` #### 📌 处理返回值 - 假设 Lua 函数返回一个字符串。 - 用 `lua_tostring(L, -1)` 获取栈顶返回值。 - 检查长度是否超过输出缓冲区 `outlen`,避免溢出。 - 若超限则报错并跳转至清理流程。 - 否则拷贝字符串到 `pout`。 - 最后 `lua_pop(L, 1)` 弹出返回值(保持栈平衡)。 --- ```c ECS_RPT_INFO("LUA GC COUNT1:%d KBytes.", lua_getgccount(rpt_lua.L)); lua_gc(rpt_lua.L, LUA_GCCOLLECT, 0); /* do garbage collector after calling */ ECS_RPT_INFO("LUA GC COUNT2:%d KBytes.", lua_getgccount(rpt_lua.L)); ``` #### 📌 触发垃圾回收 - 打印当前 Lua 堆内存使用情况(单位 KB)。 - 主动触发一次完整的垃圾回收(`LUA_GCCOLLECT`),减少内存占用。 - 再次打印内存,观察 GC 效果。 这对长时间运行的服务很重要,防止 Lua 内存泄漏累积。 --- ```c leave: pthread_mutex_unlock(&rpt_lua.lock); return rv; ``` #### 📌 统一出口 - 无论成功或失败,都解锁互斥锁。 - 返回结果状态(`ECS_OK` 或 `ECS_ERROR`)。 --- ### ✅ 总结:这个函数做了什么? | 功能 | 描述 | |------|------| | **目的** | 安全调用 Lua 脚本中的某个函数,传入模块/子模块名,获得字符串结果 | | **线程安全** | 使用 `pthread_mutex` 保证多个线程不会同时操作同一 `lua_State` | | **内存管理** | 懒加载 Lua 状态;调用后手动 GC 回收内存 | | **错误处理** | 捕获 Lua 运行时错误并记录日志 | | **防溢出** | 检查返回字符串长度是否超出缓冲区 | --- ### 🧩 示例场景 假设 Lua 中定义了如下函数: ```lua function process_event(modname, subname) if modname == "network" then return "ALERT: High traffic in " .. subname else return "INFO: Normal event in " .. modname .. "/" .. subname end end ``` 而 C 侧设置: ```c rpt_lua.function = "process_event"; ``` 当你调用: ```c ecs_rpt_lua(elk, buffer, sizeof(buffer)); ``` 就会执行对应的 Lua 逻辑,并把结果写入 `buffer`。 --- ### ❗潜在改进点(建议) 1. **返回值为空指针?** - `lua_tostring(L, -1)` 可能返回 `NULL`(如果返回值不是字符串),应加判断: ```c if (response == NULL) { ECS_RPT_ERR("Lua function did not return a string."); rv = ECS_ERROR; goto leave; } ``` 2. **使用 `strncpy` 更安全?** - 当前 `memcpy(pout, response, strlen(...))` 不会补 `\0`,应该显式添加: ```c size_t len = strlen(response); memcpy(pout, response, len); pout[len] = '\0'; ``` 或者直接 `snprintf(pout, outlen, "%s", response);` 3. **是否支持多返回值?** - 目前固定期待一个返回值,可扩展为表或其他类型。 4. **错误栈跟踪?** - 可考虑注册错误处理函数(第四个参数 `errfunc`)来提供更详细的 traceback。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值