【问题标题】:Weak table and GC finalizer using C API使用 C API 的弱表和 GC 终结器
【发布时间】:2014-07-28 01:57:27
【问题描述】:

我正在尝试通过使用 C API 将函数值存储在弱表中来为函数值创建 GC 终结器。

我从用纯 Lua 5.2 编写原型开始:

local function myfinalizer()
   print 'Called finalizer'
end

function myfunc()
   print 'Called myfunc'
end

local sentinels = setmetatable({}, { __mode='k' })
sentinels[myfunc] = setmetatable({}, { __gc=myfinalizer })

myfunc()
myfunc = nil
collectgarbage 'collect'

print 'Closing Lua'

结果输出:

Called myfunc
Called finalizer
Closing Lua


原型似乎按预期工作。下面是C版本:

#include <stdlib.h>
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

static int my_finalizer(lua_State *L)
{
    puts("Called finalizer");
    return 0;
}

static int my_func(lua_State *L)
{
    puts("Called myfunc");
    return 0;
}

int main(void)
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    // create sentinels table (weak keys) in registry
    lua_newtable(L);                                    // t
    lua_newtable(L);                                    // t mt
    lua_pushstring(L, "k");                             // t mt v
    lua_setfield(L, -2, "__mode");                      // t mt
    lua_setmetatable(L, -2);                            // t
    lua_setfield(L, LUA_REGISTRYINDEX, "sentinels");    //

    // push global function and register as sentinel
    lua_pushcfunction(L, my_func);                      // f
    lua_getfield(L, LUA_REGISTRYINDEX, "sentinels");    // f t
    lua_pushvalue(L, 1);                                // f t k
    lua_newuserdata(L, 0);                              // f t k v
    lua_newtable(L);                                    // f t k v mt
    lua_pushcfunction(L, my_finalizer);                 // f t k v mt v
    lua_setfield(L, -2, "__gc");                        // f t k v mt
    lua_setmetatable(L, -2);                            // f t k v
    lua_settable(L, -3);                                // f t
    lua_pop(L, 1);                                      // f
    lua_setglobal(L, "myfunc");                         //

    // execute test script and exit
    if (luaL_dostring(L, "myfunc(); myfunc=nil; collectgarbage'collect'")) {
        printf("Error: %s\n", lua_tostring(L, -1));
    }
    lua_gc(L, LUA_GCCOLLECT, 0);    // suggestion: two full gc cycles
    fflush(stdout);                 // suggestion: immediate flush
    puts("Closing Lua");
    lua_close(L);

    fflush(stdout);
    return EXIT_SUCCESS;
}

编译使用:

$ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -ldl -llua52 -lm

结果输出:

Called myfunc
Closing Lua
Called finalizer

C 版本有一些细微差别:

  1. 我在注册表中存储的不是本地 sentinels 表。
  2. 通过__gc 元方法使用零大小的用户数据而不是表作为标记值。

我很困惑为什么在 C 版本中 myfunc 终结器在运行完整的收集周期后不执行。我做错了什么?

【问题讨论】:

  • 尝试在原型代码中使用 userdata 代理,看看是否得到相同的行为?也尝试在 C 代码中使用表代理?从 luaL_dostring 返回后尝试调用 collect ?如果您使用两个 collectgarbage 调用,结果会改变吗?
  • @EtanReisner 感谢您的建议。从luaL_dostring返回后,我尝试添加lua_gc(L, 0, LUA_GCCOLLECT),并用表格替换用户数据,我得到了相同的结果。
  • 将 fflush 移到 lua_close 之前是否会更改输出顺序?这可能是输出刷新问题吗?虽然我还记得在某些情况下需要两个完整的周期,但我不明白为什么使用 C 会有所不同。
  • @EtanReisner 似乎没有任何效果。我根据您的建议更新了发布的 C 代码。
  • @Adam 一个小小的观察——它也可以用 Lua5.1 重现。您应该使用$ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -llua52 -ldl -lm,因为对于较新版本的 GCC - 链接参数的顺序很重要。

标签: c lua garbage-collection lua-api


【解决方案1】:

如Lua手册states

只有具有显式构造的对象才会从弱表中删除。值,例如数字和轻量级 C 函数,不受垃圾回收的影响,因此不会从弱表中删除(除非收集其关联值)。

您的my_func 被推送而没有任何上值,因此它是一个轻量级的 C 函数,并且在垃圾回收期间不会从弱表中删除,因此在您关闭 Lua 状态之前关联的用户数据不会变成垃圾。如果您使用 Lua 函数而不是 my_func,或者如果您使用 upvalues 推送 my_func(并且如果您修复了 lua_gc 调用中的参数顺序!),您的代码应该可以工作。

总而言之,以下值类型没有从弱表中移除(假设它们关联的键/值也没有被移除):

  • 布尔值
  • 数字
  • 字符串
  • 轻用户数据
  • 轻量级 C 函数(仅限 Lua 5.2)

因此,您的程序应该可以在 Lua 5.1 上正常运行,因为没有轻量级 C 函数(您仍然需要修复 lua_gc 调用)。

【讨论】:

  • 使函数成为闭包,现在可以按预期工作,感谢您的出色回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-13
相关资源
最近更新 更多