← 中文资源

简体中文版本

Python — multiprocessing 在 Windows/macOS 上报 Can't pickle

修复 spawn 启动方式下 multiprocessing 的 PicklingError 与 AttributeError,只传递可序列化参数,并守卫模块级副作用。

阅读英文原文

症状

  平台分裂就是签名——Linux CI 全绿,Windows 笔记本全红:PicklingError: Can’t pickle <function at 0x…>。spawn 模式下子进程是全新解释器,父进程必须通过 pickle 把可调用对象与参数运过去;lambda 没有可导入的名字,运输在任何工作开始之前就失败。直接 python -c “pickle.dumps(args)” 测试载荷,无需任何进程池即可复现——这是最快的隔离手段。参数里有锁、套接字、打开的文件句柄、数据库连接时,错误换成 TypeError: cannot pickle ‘_thread.RLock’ object,机制相同。

诊断思路

  先确认当前启动方式:Linux 上 fork 掩盖问题,Windows/macOS 上 spawn 强制一切过 pickle,这个差异就是全部故障面;再把失败调用约减到参数本身做最小 pickle 测试;最后审计模块顶层副作用与 name 守卫——spawn 会在每个子进程重新导入主模块,缺少守卫时每个孩子都会再启一次服务器、再加载一次大模型。

根因

  spawn 启动方式与父进程没有共享内存镜像,必须 pickle 可调用对象与参数在子进程重建。lambda、闭包、锁与打开的句柄按设计就不可 pickle,而未守卫的模块级代码会在重建期间在每个子进程重跑。

修复与验证

  让一切按构造即可 pickle:目标函数放模块顶层(可按名导入)、参数只传纯数据(dict/tuple/基本类型)、共享协调对象换成 Manager 的代理原语、模块级副作用全部用 if name == “main” 守卫。验证时在 Linux CI 显式 set_start_method(“spawn”) 跑完整池,断言成功完成;核对子进程数量符合预期;断言模块级副作用(如服务器监听)恰好执行一次而不是每子进程一次。全局强制 fork 能找回速度,但在线程与 BLAS/Cocoa 类库下会崩,并把潜伏的 pickle 约束藏出 CI 视野——只作为短期过渡。

代码示例

PicklingError: Can't pickle <function <lambda> at 0x7f8c...>:
attribute lookup <lambda> on __main__ failed
def process_batch(items):        # top-level: importable by name
    return [transform(x) for x in items]

if __name__ == "__main__":      # guard: children re-import this module
    with ProcessPoolExecutor(max_workers=4) as pool:
        results = pool.map(process_batch, chunks)  # chunks: plain data
from multiprocessing import Manager

with Manager() as mgr:
    shared = mgr.dict()   # proxy: picklable, process-safe
import multiprocessing as mp
mp.set_start_method("spawn", force=True)