防止在多处理中调用__del__

时间:2012-05-21 13:11:41

标签: python multiprocessing

在玩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 个答案:

答案 0 :(得分:3)

__del__通常用于最终确定一个对象,而不是外部资源,因此在两个进程中调用它是有意义的(当然,因为它们在fork之后都有自己的对象副本)。尝试阻止在给定进程中使用__del__并不是一个好主意,但在你真正需要的地方,真的需要它来关闭外部资源,比如文件上的文件。文件系统,它不会被fork复制,你的析构函数应该只检查预期的资源是否真的需要清理才能这样做。这可能是也可能不是“发布某种锁文件”的情况,具体取决于你实现的方式。

如果它是一个选项,您可能希望使用“with”上下文管理器来查看资源获取和释放,而不是依赖于垃圾收集的变幻莫测。

答案 1 :(得分:3)

释放锁定之类的东西不应该在__del__方法中执行,最好在with语句中将锁用作上下文管理器以确保释放锁。

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

另一个原因是:返回主进程的实例与子进程中创建的实例不同。多处理中的返回值在子进程中进行pickle,发送到父进程并在那里进行unpickled。 此流程involves a 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

答案 2 :(得分:1)

绝对不能保证__del__被称为零,一次或多次,尤其是涉及多处理时。你不应该依赖于释放锁定之类的东西。 __del__可能会在以后调用或根本不调用,并且您的发布不会发生。您应该按照建议使用上下文管理器。

还要记住,虽然CPython只会在参考周期中使用__del__方法推迟对象集合(使用循环而不是refcount gc),但不能保证其他Python实现的行为方式相同。