Почему нам нужно дважды называть Lua collectgarbage()?

Я столкнулся с несколькими местами, где люди звонят collectgarbage() дважды, чтобы завершить все неиспользуемые объекты.

Почему? Почему не достаточно одного звонка? Почему не три вызова?

Когда я пытаюсь выполнить следующий код (на Lua 5.2), объект будет финализирован (что означает: его __gc будет вызван) с помощью всего одного вызова collectgarbage:

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

Означает ли это, что одного вызова достаточно?

Ответы

Ответ 1

Это объясняется в Программе в Lua 3rd edition §17.6 Finalizers. Короче говоря, это из-за воскресения.

Финализатор - это функция, связанная с объектом, который вызывается, когда этот объект собирается собираться. 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, поэтому A невозможно собрать до завершения B. Lua должен возобновить как B, так и A перед запуском этого финализатора.

Воскресение является причиной вызова collectgarbage дважды:

Из-за воскрешения объекты с финализаторами собираются в две фазы. В первый раз, когда коллекционер обнаруживает, что объект с финализатором недоступен, сборщик воскрешает объект и ставит его в очередь для завершения. Как только его финализатор работает, Lua отмечает объект как завершенный. В следующий раз, когда коллекционер обнаружит, что объект недоступен, он удаляет объект. Если вы хотите, чтобы весь мусор в вашей программе был фактически выпущен, вы должны дважды позвонить collectgarbage; второй вызов удалит объекты, которые были завершены во время первого вызова.