为什么在禁用 CPython 垃圾收集器时会调用析构函数?

发布于 2024-08-27 21:20:53 字数 876 浏览 4 评论 0原文

我试图了解 CPython 垃圾收集器的内部结构,特别是在调用析构函数时。到目前为止,行为很直观,但以下情况让我困惑:

  1. 禁用 GC。
  2. 创建一个对象,然后删除对其的引用。
  3. 该对象被销毁并调用 _____del_____ 方法。

我认为只有启用垃圾收集器才会发生这种情况。有人可以解释为什么会发生这种情况吗?有没有办法推迟调用析构函数?

import gc
import unittest

_destroyed = False

class MyClass(object):

    def __del__(self):
        global _destroyed
        _destroyed = True

class GarbageCollectionTest(unittest.TestCase):

    def testExplicitGarbageCollection(self):
        gc.disable()
        ref = MyClass()
        ref = None
        # The next test fails. 
        # The object is automatically destroyed even with the collector turned off.
        self.assertFalse(_destroyed) 
        gc.collect()
        self.assertTrue(_destroyed)

if __name__=='__main__':
    unittest.main()

免责声明:此代码不适用于生产 - 我已经注意到,这是非常特定于实现的,并且不适用于 Jython。

I'm trying to understand the internals of the CPython garbage collector, specifically when the destructor is called. So far, the behavior is intuitive, but the following case trips me up:

  1. Disable the GC.
  2. Create an object, then remove a reference to it.
  3. The object is destroyed and the _____del_____ method is called.

I thought this would only happen if the garbage collector was enabled. Can someone explain why this happens? Is there a way to defer calling the destructor?

import gc
import unittest

_destroyed = False

class MyClass(object):

    def __del__(self):
        global _destroyed
        _destroyed = True

class GarbageCollectionTest(unittest.TestCase):

    def testExplicitGarbageCollection(self):
        gc.disable()
        ref = MyClass()
        ref = None
        # The next test fails. 
        # The object is automatically destroyed even with the collector turned off.
        self.assertFalse(_destroyed) 
        gc.collect()
        self.assertTrue(_destroyed)

if __name__=='__main__':
    unittest.main()

Disclaimer: this code is not meant for production -- I've already noted that this is very implementation-specific and does not work on Jython.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

み格子的夏天 2024-09-03 21:20:53

Python 同时具有引用计数垃圾收集和循环垃圾收集,并且后者由gc模块控制。引用计数无法禁用,因此当循环垃圾收集器关闭时仍然会发生。

由于在 ref = None 之后不再有对对象的引用,因此其 __del__ 方法会因其引用计数变为零而被调用。

文档中有一条线索:“由于收集器补充 Python 中已经使用的引用计数...”(我的重点)。

您可以通过使对象引用自身来阻止第一个断言的触发,这样它的引用计数就不会为零,例如通过给它这个构造函数:

def __init__(self):
    self.myself = self

但是如果您这样做,第二个断言将触发。这是因为使用 __del__ 方法的垃圾循环不会被收集 - 请参阅 gc.垃圾

Python has both reference counting garbage collection and cyclic garbage collection, and it's the latter that the gc module controls. Reference counting can't be disabled, and hence still happens when the cyclic garbage collector is switched off.

Since there are no references left to your object after ref = None, its __del__ method is called as a result of its reference count going to zero.

There's a clue in the documentation: "Since the collector supplements the reference counting already used in Python..." (my emphasis).

You can stop the first assertion from firing by making the object refer to itself, so that its reference count doesn't go to zero, for instance by giving it this constructor:

def __init__(self):
    self.myself = self

But if you do that, the second assertion will fire. That's because garbage cycles with __del__ methods don't get collected - see the documentation for gc.garbage.

遥远的她 2024-09-03 21:20:53

文档此处(原始链接是Python 3.5 之前的文档部分位于此处,后来重新定位)解释如何所谓的“可选垃圾收集器”实际上是一个循环垃圾收集器(引用计数无法捕获的那种)(另请参阅此处)。 此处解释了引用计数,并强调了它与循环GC:

虽然Python使用传统的
引用计数实现,它
还提供循环检测器
用于检测参考周期。这
让应用程序不用担心
创建直接或间接循环
参考;这些都是弱点
垃圾收集使用实现
仅引用计数。参考
循环由对象组成
包含(可能是间接的)引用
自己,这样每个对象
该循环有一个引用计数
是非零的。典型参考
无法计算实现
回收属于任何的记忆
引用循环中的对象,或者
从对象中引用
循环,即使没有
进一步参考循环
本身。

The docs here (original link was to a documentation section which up to Python 3.5 was here, and was later relocated) explain how what's called "the optional garbage collector" is actually a collector of cyclic garbage (the kind that reference counting wouldn't catch) (see also here). Reference counting is explained here, with a nod to its interplay with the cyclic gc:

While Python uses the traditional
reference counting implementation, it
also offers a cycle detector that
works to detect reference cycles. This
allows applications to not worry about
creating direct or indirect circular
references; these are the weakness of
garbage collection implemented using
only reference counting. Reference
cycles consist of objects which
contain (possibly indirect) references
to themselves, so that each object in
the cycle has a reference count which
is non-zero. Typical reference
counting implementations are not able
to reclaim the memory belonging to any
objects in a reference cycle, or
referenced from the objects in the
cycle, even though there are no
further references to the cycle
itself.

嘦怹 2024-09-03 21:20:53

根据您对垃圾收集器的定义,CPython 有两个垃圾收集器,一个是引用计数收集器,另一个是引用计数收集器。
引用计数器始终工作,并且无法关闭,因为它是一种非常快速且轻量级的计数器,不会显着影响系统的运行时间。
另一种(我认为是标记和清除的某种变体)经常运行,并且可以禁用。这是因为它需要解释器在运行时暂停,而这可能会在错误的时刻发生,并消耗大量的 CPU 时间。
当您希望做一些时间紧迫的事情时,可以禁用它,并且缺少此 GC 不会给您带来任何问题。

Depending on your definition of garbage collector, CPython has two garbage collectors, the reference counting one, and the other one.
The reference counter is always working, and cannot be turned off, as it's quite a fast and lightweight one that does not sigificantly affect the run time of the system.
The other one (some varient of mark and sweep, I think), gets run every so often, and can be disabled. This is because it requires the interpreter to be paused while it is running, and this can happen at the wrong moment, and consume quite a lot of CPU time.
This ability to disable it is there for those time when you expect to be doing something that's time critical, and the lack of this GC won't cause you any problems.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文