Python 3.8 多进程:TypeError: 无法pickle 'weakref'对象

3
我们正在尝试使用 multiprocessing 模块来执行这段代码:
import multiprocessing as mp

ctx = mp.get_context("spawn")
(child, pipe) = ctx.Pipe(duplex=True)
job_process = ctx.Process(
    name="my_job",
    target=job_func,
    args=(
        child,
        server_info,
        manager,
        job_config,
        config_file,
    ),
)
job_process.start()

我们在Python 3.8中遇到了以下错误,而在Python 3.6中没有看到:

 Traceback (most recent call last):
   File "path/to/venv/lib/python3.8/site-packages/path/to/file.py", line 137, in includeme 
     job_process.start()
   File "/usr/lib/python3.8/multiprocessing/process.py", line 121, in start
     self._popen = self._Popen(self)
   File "/usr/lib/python3.8/multiprocessing/context.py", line 284, in _Popen
     return Popen(process_obj)
   File "/usr/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 32, in __init__
     super().__init__(process_obj)
   File "/usr/lib/python3.8/multiprocessing/popen_fork.py", line 19, in __init__
     self._launch(process_obj)
   File "/usr/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 47, in _launch
     reduction.dump(process_obj, fp)
   File "/usr/lib/python3.8/multiprocessing/reduction.py", line 63, in dump
     ForkingPickler(file, protocol).dump(obj)
 TypeError: cannot pickle 'weakref' object

这个过程的启动方式或提供的参数是否需要更改或检查?任何方向都会受到赞赏!

1个回答

3
这个问题很可能是你的应用程序中一个更大bug的副作用。在使用“spawn”模式时,你必须使用导入保护来使用multiprocessing,否则会发生一些奇怪的事情。特别是:
  1. 在spawn模式下进行的fork仿真将尝试序列化一些主模块的状态以传输到子进程,以类似地初始化子进程;截至Python 3.7,multiprocessing.Process本身不可pickle(虽然有一个补丁待修复),因此您绝对不希望在任何可能被pickled的地方使用它。

  2. 通过不使用导入保护,子进程对主模块的导入会执行主模块执行的所有操作,包括启动一个子进程(它本身启动一个子进程等等,无限循环)。

通过使用导入保护并将所有内容放入调用保护内的main函数中,可以解决这两个问题:
import multiprocessing as mp

# Wrapping in function avoids implicit dependencies on mutable global state
def main():
    ctx = mp.get_context("spawn")
    (child, pipe) = ctx.Pipe(duplex=True)
    job_process = ctx.Process(
        name="my_job",
        target=job_func,
        args=(
            child,
            server_info,
            manager,
            job_config,
            config_file,
        ),
    )
    job_process.start()

# Guard ensures only the parent process tries to actually run main()
if __name__ == '__main__':
    main()

我明白了。两个快速问题:1)这是否与从Python 3.6到3.8的更改有关?因为我们在3.6中没有看到这个错误,而 2)我提供的代码片段封装在一个“includeme”函数中,该函数由Pyramid在服务器启动时启动,更多细节请参见此处:https://docs.pylonsproject.org/projects/pyramid/en/latest/api/config.html。我需要进一步包装另一个函数吗? - aaron02
所以你的代码在正常使用下是“工作”的,但我的心理调试告诉我,job_funcserver_infomanagerjob_configconfig_file中的一个秘密地引用了一个Process对象(可能通过多层引用),因此无法被pickle化(您可以尝试手动逐个pickle它们以找出哪个是)。这就是为什么[MCVE]很重要的原因;我实际上无法重现您的问题,所以我所能做的就是猜测您可能已经做了什么而没有向我展示。 - ShadowRanger
ShadowRanger:非常感谢您详细的解释!您提供的GH问题和PR链接确实让人对所有这些有很好的理解! - aaron02
经过进一步检查,manager是一个类的实例,其__init__方法调用另一个方法来创建一个multiprocessing.Process。这是通过另一个在导致weakref错误的特定代码片段的config.include之前的config.include来启动的。这两个都需要在Pyramid服务器启动时使用config.include进行初始化。您有什么建议可以在includeme函数中实现或检查导入保护,以便config.include调用它? - aaron02
如果你真的担心,在你的代码中加入import sys,然后执行 print(sys.modules['__main__']) 来找出实际作为入口点的文件,并手动检查该文件。如果它在函数外部进行任何导入和模块初始化操作,而没有导入保护,则可能存在问题,但很可能不存在。 - ShadowRanger
显示剩余8条评论

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