防止在多进程中调用 __del__ 方法

3

在尝试使用multiprocessing时,我注意到在以下的脚本中,__del__被调用了两次(一次在子进程中,一次在父进程中)。

class myclass(object):

    def __init__(self,val):
        self.val=val
        print ("Initializing %s"%str(self.val))

    def __del__(self):
        print ("deleting %s"%str(self.val))


if __name__ == "__main__":
    import multiprocessing
    p=multiprocessing.Pool(4)
    obj_list=p.map(myclass,range(30))
    raw_input()

对于这个脚本,它并不重要……但如果__del__有副作用呢?(我能想到的一个可能的例子是释放某种锁文件)。有没有办法防止__del__被调用两次?
3个回答

3

像释放锁这样的操作不应该在__del__方法中执行,最好使用锁作为上下文管理器在with语句中确保释放锁。

此外,生成的类实例存在于不同的进程中,在子进程中更改任何内容都不会影响其他子进程,当然,除了用于IPC的对象(例如来自模块的队列、管道、锁等)。

还有一件事是:返回给主进程的实例不是在子进程中创建的相同实例。在中,返回值在子进程中被pickle,然后发送到父进程并在那里进行unpickle。 这个过程涉及一个ForkingPickler*。 因此,__del__不会在同一实例上多次调用,而是在不同子进程中的不同实例上调用。

*:不完全确定这里到底发生了什么,也许有人知道得更多……但也许另一个版本的示例会有所帮助:

import multiprocessing
import time
import os

class myclass(object):

    def __init__(self,val):
        self.val=val
        print ("Initializing %s - %s - %s" % (str(self.val), self, os.getpid()))

    def __del__(self):
        print ("Deleting     %s - %s - %s" % (str(self.val), self, os.getpid()))

if __name__ == "__main__":
    p = multiprocessing.Pool(3)
    obj_list=p.map(myclass,range(5))
    del p
    for o in obj_list:
        print ("Returned     %s - %s - %s" % (str(o.val), o, os.getpid()))

输出:

Initializing 0 - <__main__.myclass object at 0x7f2497fdc0d0> - 7574
Initializing 2 - <__main__.myclass object at 0x7f2497fdc110> - 7574
Deleting     0 - <__main__.myclass object at 0x7f2497fdc0d0> - 7574
Initializing 1 - <__main__.myclass object at 0x7f2497fdc150> - 7575
Initializing 3 - <__main__.myclass object at 0x7f2497fdc1d0> - 7575
Deleting     1 - <__main__.myclass object at 0x7f2497fdc150> - 7575
Initializing 4 - <__main__.myclass object at 0x7f2497fdc0d0> - 7574
Deleting     2 - <__main__.myclass object at 0x7f2497fdc110> - 7574
Returned     0 - <__main__.myclass object at 0x7f2497fdc650> - 7573
Returned     1 - <__main__.myclass object at 0x7f2497fdc7d0> - 7573
Returned     2 - <__main__.myclass object at 0x7f2497fdc810> - 7573
Returned     3 - <__main__.myclass object at 0x7f2497fdc850> - 7573
Returned     4 - <__main__.myclass object at 0x7f2497fdc890> - 7573
Deleting     3 - <__main__.myclass object at 0x7f2497fdc1d0> - 7575
Deleting     4 - <__main__.myclass object at 0x7f2497fdc890> - 7573
Deleting     3 - <__main__.myclass object at 0x7f2497fdc850> - 7573
Deleting     2 - <__main__.myclass object at 0x7f2497fdc810> - 7573
Deleting     1 - <__main__.myclass object at 0x7f2497fdc7d0> - 7573
Deleting     0 - <__main__.myclass object at 0x7f2497fdc650> - 7573

注意不同的进程和对象ID


3
__del__通常用于结束一个对象,而不是外部资源,因此在两个进程中都调用它是有意义的(因为当然,在fork之后,两个进程都有自己的对象副本)。在给定的进程中尝试防止使用__del__并不是一个好主意,但在真正需要关闭像文件系统上的文件这样的外部资源时,你的析构函数应该检查一下是否真的需要清理预期的资源,然后再进行清理。对于“释放某种锁文件”,这可能是或可能不是这种情况,这取决于你如何实现它。
如果可以的话,您可能希望使用“with”上下文管理器来进行资源获取和释放,而不是依赖于垃圾回收机制的各种变化。

1

绝对没有保证 __del__ 被调用零次、一次或多次,特别是当你涉及到多进程时。你不应该依赖它来释放锁之类的东西。__del__ 可能会被延迟很久或根本不被调用,导致你的释放操作无法执行。建议使用上下文管理器。

此外,请记住,虽然 CPython 仅会推迟具有 __del__ 方法的对象在引用循环中的收集(使用循环而不是引用计数垃圾回收),但不能保证其他 Python 实现会以相同的方式运行。


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