Python multiprocessing模块跨环境行为差异及Windows报错排查
我来帮你拆解这个Windows下多进程报错的问题,核心原因其实是Windows和Linux(还有不同Python版本的Mac)的多进程启动机制差异,咱们一步步来看:
为什么Windows会报错,Linux和在线编译器却正常?
Linux下Python多进程默认用fork模式——它会直接复制父进程的整个内存空间,不需要把对象序列化(pickle)传给子进程;但Windows用的是spawn模式,会启动一个全新的Python解释器,这时候就需要把进程相关的对象(包括你的A类实例)序列化后传递给子进程。
你看到的TypeError: cannot pickle 'weakref' object错误,就是因为当你把multiprocessing.Process实例存在self.g、self.d这类实例属性里时,启动进程需要序列化整个A实例,可Process对象内部包含了不能被pickle的弱引用(weakref),直接触发了序列化失败。
为什么有的case正常,有的case报错?
咱们逐个看你的测试case:
- 正常的case(1、2、4、6):这些要么是把进程对象存在局部变量里(比如case1的
a、case6的h),局部变量不会被包含在实例的序列化内容里;要么是先启动实例属性里的进程,再覆盖这个属性(比如case2的self.b)——当启动第二个进程时,第一个进程对象已经不再是实例的属性了,序列化时不会被包含进去。 - 报错的case(3、5):case3里你先启动了局部进程
c,然后创建self.d并启动——此时self.d是实例属性,序列化self时,实例关联的进程对象(包括c持有的self引用)导致了无法pickle的内容;case5更直接,你把进程列表直接存在self.g里,启动时序列化整个实例,必然会碰到不能被pickle的Process对象。
解决办法
给你几个可行的方案,任选其一就行:
- 尽量用局部变量存储进程对象:像case1、6那样,不把Process实例存在类的属性里,自然就不会触发实例序列化的问题。
- 先启动进程,再赋值给实例属性:如果必须把进程存在实例里,调整顺序,先启动进程,再把它存到
self的属性中,比如把case5改成这样:
# 修改后的case5 g = [multiprocessing.Process(target=self.t, args=()) for _ in range(2)] for proc in g: proc.start() self.g = g # 启动后再赋值给实例属性
这样启动进程时,self里还没有这个进程列表,序列化self就不会包含Process对象,自然不会报错。
3. 用静态/类方法作为进程目标函数:如果你的进程方法不需要访问实例的属性,把t改成静态方法,这样就不需要传递self实例,也就不用序列化整个对象了:
class A: def __init__(self): self.g = [multiprocessing.Process(target=self.t, args=()) for _ in range(2)] for proc in self.g: proc.start() time.sleep(1) @staticmethod def t(): print("Hi")
Mac OS的测试预测
Mac OS从Python 3.8开始默认也是用spawn模式(和Windows一致),所以你的case3和case5在Python 3.8+的Mac上会出现和Windows一样的错误;如果是Python 3.7及更早的Mac版本,默认用fork模式,就会和Linux一样所有case都正常运行。
内容的提问来源于stack exchange,提问作者HaroldDeng

