【问题标题】:Lua weak tables memory leakLua 弱表内存泄漏
【发布时间】:2020-08-04 23:08:21
【问题描述】:

我不经常使用弱表。但是现在我需要管理我的对象的某些属性,这些属性应该存储在其他地方。那是弱表派上用场的时候。我的问题是,它们不能按预期工作。我需要弱键,以便在不再引用键并且我需要强值时删除整个键/值对,因为存储的是具有元信息的表,这些表仅在该表内使用,它也有对密钥的引用,但不知何故,这些对永远不会被收集。

代码示例:


local key = { }
local value = {
        ref = key,
        somevalue = "Still exists"
}

local tab = setmetatable({}, { __mode = "k" })

tab[key] = value

function printtab()
        for k, v in pairs(tab) do
                print(v.somevalue)
        end
end

printtab()

key = nil
value = nil

print("Delete values")
collectgarbage()

printtab()

预期输出:

Still exists
Delete values

得到:

Still exists
Delete values
Still exists

为什么没有删除键/值对?对 value 的唯一引用实际上是 tab 内的弱引用,并且 value 内的引用不相关,因为 value 本身没有在任何地方使用。

【问题讨论】:

  • 你的 Lua 版本是多少?
  • @EgorSkriptunoff Lua 5.1

标签: memory-leaks lua garbage-collection weak-references


【解决方案1】:

从 Lua 5.2 开始支持 Ephemeron 表。
Lua 5.2 手册说:

具有弱键和强值的表也称为 ephemeron 表。在一个 ephemeron 表中,一个值只有当它的键是可达时才被认为是可达的。特别是,如果对键的唯一引用来自其值,则该对将被删除。

Lua 5.1 不正确支持 ephemeron 表。

【讨论】:

    【解决方案2】:

    您对垃圾收集器做出了太多假设。 最终将收集您的数据。在这个特定的例子中,如果你调用collectgarbage() 两次,它应该可以工作,但如果你的弱表中有一些循环,它可能需要更长的时间。

    编辑:这实际上只在您等待__cg 事件时才重要


    我更详细地检查了您的代码,发现您还有另一个问题。

    你的 value 也引用了键,创建了一个循环,这对于你的 Lua 版本的 GC 来说可能太多了,无法处理。在 PUC Lua 5.3 中,这按预期工作,但在 LuaJIT 中,循环似乎阻止了值被收集。

    如果你仔细想想,这实际上很有意义;据我所知,整个事情的工作原理是,当它们没有被其他任何地方引用时,首先从表中删除弱元素,从而让它们在下次 GC 运行时正常收集。

    但是,当这一步运行时,键仍在表中,因此(非弱)值在 GC 眼中是有效的引用,因为它可以从代码中访问。所以 GC 会陷入死锁,无法删除键值对。

    可能的解决方案是:

    1. 不要在值中保存对键的引用
    2. 将该值也设为弱表,这样它也不会算作参考
    3. 升级到另一个 Lua 版本
    4. 将引用包装在弱值单元素数组中

    【讨论】:

    • 所以基本上我的 Lua 版本的 gc 不够智能,无法注册密钥实际上不再可用?
    • 是的,或者更确切地说,键有效可用,但是一旦收集到对应的值,就不会了,这就是Lua的一部分显然不考虑。
    【解决方案3】:

    您可以像这样更改代码。然后你会得到预期的输出。提示:当你希望它是星期时,不要引用键变量。

    local key = { }
    local value = {
            -- ref = key,
            somevalue = "Still exists"
    }
    
    local tab = setmetatable({}, { __mode = "k" })
    
    tab[key] = value
    
    function printtab()
            for k, v in pairs(tab) do
                    print(v.somevalue)
            end
    end
    
    printtab()
    
    key = nil
    value = nil
    
    print("Delete values")
    collectgarbage()
    
    printtab()
    

    【讨论】:

    • 如果 OP 将该引用放在表中,我认为这样做是有充分理由的。这只是一些简化的示例代码,也许 OP 真正的代码需要以某种方式引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 2017-01-23
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多