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__被调用两次?

4

3 回答 3

3

释放锁之类的事情不应该是在__del__方法中做的事情,最好在语句中使用锁作为上下文管理器with来确保锁的释放。

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

另一件事是:返回给主流程的实例与子流程中创建的实例不同。多处理中的返回值在子进程中被腌制,发送到父进程并在那里取消腌制。此过程涉及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

于 2012-05-21T14:08:31.467 回答
3

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

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

于 2012-05-21T13:54:05.143 回答
1

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

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

于 2012-05-21T15:54:26.573 回答