为什么我们需要调用Lua的collectgarbage()函数两次?

20

我在多个地方遇到过,人们会调用collectgarbage()两次来完成所有未使用对象的终结。

为什么呢?单次调用不足以完成任务吗?那么三次呢?

当我尝试以下代码(在Lua 5.2上),这个对象就被终结了(意思是:它的__gc函数被调用)只需要一次对collectgarbage的调用:

do
  local x = setmetatable({},{
    __gc = function() print("works") end
  })
end
collectgarbage()
os.exit()

这是否意味着只需要一个电话?

1个回答

18
在《Lua编程》第三版的第17.6节“终结器”中有详细解释。简而言之,这是由于对象复活引起的。
终结器是与对象相关联的函数,当该对象即将被回收时调用。Lua使用__gc元方法来实现终结器。
问题在于,在某些情况下,调用终结器时对象必须仍然存活。PiL通过以下示例来解释:
A = {x = "this is A"}
B = {f = A}
setmetatable(B, {__gc = function (o) print(o.f.x) end})
A, B = nil
collectgarbage() --> this is A
B 的终止器访问了 A,因此在 B 终止之前无法回收 A。Lua 必须先将 BA 都复活后,才能运行该终止器。
复活是调用 collectgarbage 两次的原因:
由于复活,具有终止器的对象分两个阶段被回收。 第一次收集器检测到不可达的具有终止器的对象时,收集器会使其复活并排队等待终止。一旦运行了它的终止器,Lua 就会将该对象标记为已终止。下一次收集器检测到对象不可达时,将删除该对象。如果要确保程序中所有垃圾都被释放,必须调用 collectgarbage 两次;第二次调用将删除在第一次调用期间终止的对象。

引用“第二次调用将删除在第一次调用期间已完成的对象”(以及“下次收集器检测到[...]它将删除该对象”)表示,如果我理解正确,则终结器(__gc)在对collectgarbage()第一次调用结束时运行。然后,对coollectgarbage第二次调用只是释放内存(手册:“对象内存在下一个垃圾回收周期中被释放”。PiL:请参见上面的引用)。我的示例(我的问题中的代码)据说“证明”了我对此的理解。但是... - Niccolo M.
但是我的理解一定是错误的,因为我的第二个链接证明了它是错误的(即,在第一个collectgarbage上没有调用终结器;请参见“我不得不添加一个更多的collectgarbage()调用”),而第一个链接包含一个评论(由用户)说“[调用]两次确保所有终结器都被调用”(也许这个评论是错误的)。所以我仍然不知所措。 - Niccolo M.
哎呀,我看到我的错误了。我的第一个链接处理的是lua 5.1.4,它没有终结器,所以对我们来说完全无关。抱歉。 - Niccolo M.
所以,我希望能够清楚地确认我的理解:__gc 在第一次调用collectgarbage()时被调用,对吗?如果我理解有误,那么我是如何误解引号的呢?(顺便说一句,我确实阅读了你的代码示例。) - Niccolo M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接