在玩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__
被调用两次?
答案 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实现的行为方式相同。