来自帖子https://stackoverflow.com/a/53504673/9191338:
生成对象被克隆,并且每个对象都在每个进程中最终确定
fork/forkserver 对象在主进程中共享并最终确定
情况似乎是这样:
import os
from multiprocessing import Process
import multiprocessing
multiprocessing.set_start_method('fork', force=True)
class Track:
def __init__(self):
print(f'{os.getpid()=} object created in {__name__=}')
def __getstate__(self):
print(f'{os.getpid()=} object pickled in {__name__=}')
return {}
def __setstate__(self, state):
print(f'{os.getpid()=} object unpickled in {__name__=}')
return self
def __del__(self):
print(f'{os.getpid()=} object deleted in {__name__=}')
def f(x):
print(f'{os.getpid()=} function executed in {__name__=}')
if __name__ == '__main__':
x = Track()
for i in range(2):
print(f'{os.getpid()=} Iteration: {i}, Process object created')
p = Process(target=f, args=(x,))
print(f'{os.getpid()=} Iteration: {i}, Process created and started')
p.start()
print(f'{os.getpid()=} Iteration: {i}, Process starts to run functions')
p.join()
输出为:
os.getpid()=30620 object created in __name__='__main__'
os.getpid()=30620 Iteration: 0, Process object created
os.getpid()=30620 Iteration: 0, Process created and started
os.getpid()=30620 Iteration: 0, Process starts to run functions
os.getpid()=30623 function executed in __name__='__main__'
os.getpid()=30620 Iteration: 1, Process object created
os.getpid()=30620 Iteration: 1, Process created and started
os.getpid()=30620 Iteration: 1, Process starts to run functions
os.getpid()=30624 function executed in __name__='__main__'
os.getpid()=30620 object deleted in __name__='__main__'
确实该对象仅在主进程中被删除。
我的问题是,这是如何实现的?虽然新进程是从主进程fork出来的,但是fork之后,新进程本质上是另一个进程,这两个进程怎么能共享gc信息呢?
此外,gc 信息共享是否发生在每个对象上,还是仅发生在作为子进程参数传递的对象上?
我认为您将使用启动方法“fork”调用
subprocess.Process
与直接调用 os.fork
混淆了。 if __name__
块不会在子进程中重新输入:suprocess 代码负责样板代码,以便仅调用您的目标函数。
“正在删除的对象”不会打印在子进程上的原因是,该样板代码必须保留对该对象的引用:它没有机会运行其
__del__
,因为子进程本身已在 关闭.join
。
所以,请耐心等待:(1)“x”是在父进程中创建的。 (2)父进程被fork:“x”实例存在于子进程中,不需要序列化或反序列化,它只是Python解释器状态的一部分,在fork上被克隆; (3) 子进程模块中的管理代码识别到这一点,并简单地将其获得的对子进程中“x”克隆的引用传递给目标函数。此管理代码在子进程本身中引用了“x” - 当目标函数返回时,整个子进程将关闭:出于某种原因,克隆实例上的
__del__
没有机会运行。抱歉,文档说 __del__
不可靠(“不能保证为解释器退出时仍然存在的对象调用 __del__()
方法。” - ),我不确定为什么分叉版本子进程不会给它运行的机会,这在某种程度上是您问题的核心 - 但是子进程中的x
与父进程x
不同。它是一个独特的实例,并且引用独立计数。只是 __del__
要么不运行,要么其输出被抑制 - 因为它是在解释器关闭时调用的,甚至可能“print”在内置函数中不再可用,并且生成的 NameError 异常被简单地抑制(这也有记录)。事实上__del__
并不那么可靠,它甚至导致了“上下文管理器”协议(
with
命令)的创建:如果__del__
是可靠的,则不需要它,终结器对象中的代码 __del__
将始终运行,并且不需要额外的 __exit__
方法。所以,这是你自己的代码 - 我刚刚在__new__
中添加了一个新的打印,以便你可以检查:它确实只在根进程上创建一次,并添加了一个在子进程上修改的属性,所以你可以看到它没有在父进程上修改:
import os
from multiprocessing import Process
import multiprocessing
import threading
class Track:
def __new__(cls):
print("NEW Track")
return super().__new__(cls)
def __init__(self, level=0):
self.count = 0
print(f'{os.getpid()=} object created in {__name__=}')
def __getstate__(self):
print(f'{os.getpid()=} object pickled in {__name__=}')
return {}
def __setstate__(self, state):
print(f'{os.getpid()=} object unpickled in {__name__=}')
return self
def __del__(self):
print(f'{os.getpid()=} object deleted in {__name__=}')
def f(x):
print(f'{os.getpid()=} function executed in {__name__=}')
x.count = 1
if __name__ == '__main__':
x = Track()
for i in range(2):
print(f'{os.getpid()=} Iteration: {i}, Process object created')
p = Process(target=f, args=(x,))
print(f'{os.getpid()=} Iteration: {i}, Process created and started')
p.start()
print(f'{os.getpid()=} Iteration: {i}, Process starts to run functions')
p.join()
print(x.count)
现在,如果您想要一个复杂的机制来跨进程实际同步对象属性(以及列表和字典中包含的对象),可以使用“subprocessing.Manager”
- 它添加一个辅助子进程并在后台执行很多操作以便某些对象在不同进程之间保持同步。它是如此“神奇”,我从未见过它在“现实世界”中使用,在实践中更喜欢 multiprocessing.Queue
更简单直接的解决方案。 (我的意思是,有些人和项目可能会使用经理,但我没有看到,而且它们看起来很复杂)。不过,您会喜欢用您的
Manager
实验来玩弄 print pid
- 去做吧,如果您对这个主题感兴趣,您可以学到很多东西。