Python多进程报错:AttributeError: 无法获取模块中的calc函数
多进程Pool.map抛出AttributeError的原因及解决方案
问题复现
运行以下代码时出现AttributeError: Can't get attribute 'calc' on <module '__main__' ...>:
import numpy as np, pandas as pd import multiprocessing, itertools, timeit from functools import partial processes = 5 * multiprocessing.cpu_count() print(f'processes: {processes}') pool = multiprocessing.Pool(processes=processes) def calc(x, y): return x+y def calc_all(): pairs = [[1,1], [2,2], [3,3]] results = pool.map(calc, pairs) print(results) if __name__ == '__main__': calc_all()
错误栈:
File "/usr/local/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap self.run() File "/usr/local/lib/python3.12/multiprocessing/process.py", line 108, in run self._target(*self._args, **self._kwargs) File "/usr/local/lib/python3.12/multiprocessing/pool.py", line 114, in worker task = get() ^^^^^ File "/usr/local/lib/python3.12/multiprocessing/queues.py", line 389, in get return _ForkingPickler.loads(res) ^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: Can't get attribute 'calc' on <module '__main__' from '/workspaces/calc.py'>
问题原因
这是Unix/Linux默认的多进程fork模式下的模块加载顺序问题:
- 主进程在全局作用域创建了
Pool对象,这部分代码不在if __name__ == '__main__':块内。 - 子进程启动时,会重新导入主模块(
__main__)来获取任务中用到的函数对象。 - 子进程导入主模块时,会先执行全局代码:先创建
Pool,然后才定义calc函数。 - 当子进程尝试反序列化主进程传来的
calc函数时,此时子进程的__main__模块中还没定义calc,所以Pickler找不到这个属性,抛出错误。
另外原代码还有隐藏问题:pool.map(calc, pairs)会把每个[1,1]列表作为单个参数传给calc,但calc需要两个独立参数,后续还会触发参数不匹配错误。
无需拆分模块的解决方案
核心是把Pool的创建逻辑移到if __name__ == '__main__':块内,避免子进程重复执行Pool创建代码,同时保证子进程导入模块时函数已被定义,顺便修正参数传递问题:
import numpy as np, pandas as pd import multiprocessing, itertools, timeit from functools import partial # 先定义所有函数,确保子进程导入时能找到 def calc(x, y): return x+y def calc_all(pool): pairs = [[1,1], [2,2], [3,3]] # 用starmap解包每个列表的元素作为calc的两个参数 results = pool.starmap(calc, pairs) print(results) if __name__ == '__main__': # 仅主进程创建Pool processes = 5 * multiprocessing.cpu_count() print(f'processes: {processes}') pool = multiprocessing.Pool(processes=processes) calc_all(pool) # 显式关闭Pool并等待子进程完成 pool.close() pool.join()
为什么拆分模块能解决问题
当把calc放到单独模块时,子进程导入该模块时,模块内的函数是按顺序定义好的,不存在全局代码执行顺序的问题,Pickler可以直接找到calc函数,因此不会报错。
内容的提问来源于stack exchange,提问作者mike01010
相关产品推荐
相关产品推荐

