joblib.Parallel切换threading/multiprocessing后端结果异常问题
问题根因
两个后端的内存模型差异是核心原因:
threading后端基于多线程实现,所有工作线程和主进程共享同一块内存地址空间,传入的out_list就是主进程中的原始对象,子线程修改列表元素的操作会直接作用在原对象上,因此主进程最终打印能拿到正确值。multiprocessing后端基于多进程实现,每个工作进程是独立的操作系统进程,拥有完全独立的内存空间。任务分发时,out_list会被序列化后拷贝一份传给每个子进程,子进程修改的只是自己内存里的列表副本,主进程中的原始out_list从未被修改,因此最终打印全是初始化的None。
你观察到的三类表现对应逻辑:
- 运行速度大幅提升:多进程可以绕过Python GIL全局解释器锁,CPU密集型场景下性能远高于受GIL限制的多线程,属于正常表现。
- 任务函数内print输出正确:子进程修改的是自己持有的列表副本,副本内存储的计算值本身是正确的,因此打印结果正常。
- 最终print全为None:主进程的原列表没有收到任何子进程的修改,始终保留初始化的
None值。
修复方案
不要通过传入可变对象、在任务内部修改的方式收集结果——Parallel本身会按任务执行顺序收集所有被装饰函数的返回值,直接利用返回值组装结果是多进程并行的标准写法,没有额外的进程通信开销,性能最优。
修复后的可运行代码:
from joblib import Parallel, delayed def E_th(i, tt): calc_res = tt + i print(calc_res) return tt, calc_res # 返回索引和对应计算结果,方便后续组装列表 if __name__ == "__main__": time_range = range(0, 10) for i in range(0, 2): out_list = [None] * len(time_range) # 直接收集Parallel返回的所有任务结果,无需传入out_list task_results = Parallel(n_jobs=64, backend='multiprocessing')( delayed(E_th)(i, tt) for tt in time_range ) # 按索引将结果填充到out_list for idx, val in task_results: out_list[idx] = val print(out_list)
如果有强需求必须在多进程间共享可变列表,需要使用multiprocessing模块提供的跨进程共享结构(比如Manager().list()、共享内存Array),但这类方案会引入进程间序列化、通信的额外开销,性能远低于直接收集返回值的方案,非必要不推荐使用。
内容的提问来源于stack exchange,提问作者Billy Matlock
相关产品推荐
相关产品推荐

