2010-04-05 73 views
6

我想了解CPython垃圾收集器的内部,特别是当析构函数被调用时。到目前为止,这种行为很直观,但下面的案例让我想起了:为什么在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中不起作用。

回答

9

Python有两个引用计数垃圾收集和循环垃圾收集,并是后者的gc模块控制。引用计数无法禁用,因此在循环垃圾收集器关闭时仍会发生。

由于ref = None之后没有任何引用留给您的对象,因此其方法__del__的引用计数为零。

the documentation有一个线索:“由于收集器补充引用计数已在Python中使用......”(我强调)。

您可以通过将对象引用自身,以便其引用计数并不变为零,例如通过给它这个构造射击停止第一个断言:

def __init__(self): 
    self.myself = self 

但是,如果你那样做,第二个断言就会开始。这是因为垃圾周期__del__方法不收集 - 请参阅gc.garbage的文档。

4

根据您的垃圾收集器的定义,CPython有两个垃圾收集器,引用计数一个,另一个。
引用计数器始终在工作,并且无法关闭,因为它是相当快速和轻量级的计数器,不会对系统的运行时间产生显着影响。
另一个(我认为一些标记和扫描的变化)会每隔一段时间运行一次,并且可以禁用。这是因为它需要解释器在运行时暂停,这可能发生在错误的时刻,并且消耗相当多的CPU时间。
当你期望做某些时间紧迫的事情时,这种禁用它的能力就存在了,而缺乏这种GC不会引起任何问题。

+0

这个“两个垃圾收集器”的实现记录在某处吗? – Frederik 2010-04-05 12:42:48

+0

看看Alex Martelli的回答及其相关链接。这可能比我能想出的其他任何东西都要好。 – 2010-04-05 17:45:41

4

文档here解释了所谓的“可选垃圾回收器”实际上是一个收集器循环垃圾(引用计数不会捕获的类型)。引用计数解释here,点头其相互作用与循环GC:

虽然Python使用传统 的引用计数实现,它 还提供了一个周期检测器 工程检测的参考周期。这个 允许应用程序不用担心 创建直接或间接循环 引用;这些都是 垃圾收集的弱点,使用 只能引用计数。参考 周期由 自身包含(可能间接)引用 的对象组成,因此每个对象在 周期中的引用计数为 非零。典型参考 计数实现方式中不能够 收回属于任何 对象在参考周期内,或从 在 周期的对象所引用的存储器,即使有对循环 本身没有 进一步的引用。