【问题标题】:Why isn't the __del__ method called?为什么不调用 __del__ 方法?
【发布时间】:2017-10-26 14:53:20
【问题描述】:

在python3的multiprocess中,不能调用__del__方法。

我已经阅读了有关循环引用的其他问题,但在multiprocess 中找不到情况。

foo中存在循环引用,直接调用foo时会调用__del__,但multiprocess中永远不会调用__del__

import multiprocessing
import weakref


class Foo():
    def __init__(self):
        self.b = []

    def __del__(self):
        print ('del')


def foo():
    print ('call foo')
    f = Foo()
    a = [f]
    # a = [weakref.ref(f)]
    f.b.append(a)


# call foo in other process
p = multiprocessing.Process(target=foo)
p.start()
p.join()


# call foo
foo()

输出:
调用 foo
调用 foo
删除

为什么在p 中没有调用__del__

【问题讨论】:

  • 你认为weakref 是做什么的?你在代码中提到它,它与这个问题有关,但你没有说为什么你认为它是相关的。

标签: python python-3.x python-multiprocessing circular-reference finalizer


【解决方案1】:

分叉的Process 对象在使用os._exit() 运行其任务后终止,这将强制终止子进程没有 Python 在退出时执行的正常清理。循环垃圾没有被清理(因为进程在没有给循环 GC 运行机会的情况下终止),它只是掉在地板上,让操作系统来清理。

这是故意的,因为正常退出(调用所有正常的清理过程)会冒一些风险,例如未刷新的缓冲区在父级和子级中都被刷新(输出加倍),以及当分叉进程继承父进程的所有状态时涉及的其他奇怪问题但除非明确告知这样做,否则不应使用它。

【讨论】:

    【解决方案2】:

    问题实际上不是为什么它在multiprocess 被调用,而是为什么它在另一个示例中 调用。答案是当您调用foo 时不会调用它。它在程序结束时调用。由于程序已经完成,Python 知道即使它仍然被引用,也可以清理其他任何内容,因此它会清理循环引用。

    如果您在脚本末尾添加 print 语句,或者从 REPL 调用它,您可以看到在您的第二次 foo 调用中仍然没有调用 __del__,但仅在脚本结束。

    鉴于 Python 会在脚本结束时清理循环引用,ShadowRanger's answer 解释了为什么在多处理函数完成时不会发生这种情况。

    【讨论】:

    • Python 会间歇性地清理循环引用,而不仅仅是在脚本终止时。大部分细节都记录在the gc module 上,但简短的版本是它按年龄维护受监控的引用池,并更频繁地清理新池,不经常清理旧池,间隔由分配数量和执行的释放。也就是说,只要存在一个活动(非循环)引用,循环收集就无关紧要(因为它还不是循环垃圾)。
    • 我小心翼翼地没有说它只是发生在那个时候,但我确实暗示了它。我不确定,但我认为它通常只发生在那个时候,但是如果你愿意,你可以在gc 中调用一些东西,或者它可能会检查你是否内存不足。很高兴知道它有时会在结束之前发生。