如何并行化多输入多输出函数的Python for循环?
我明白你的困惑——很多Python多进程示例确实都盯着单输出函数讲,碰到多输入多输出的场景就有点摸不着头脑。不过针对你这个循环的并行化,其实用concurrent.futures或者multiprocessing库都能轻松搞定,下面我给你具体的实现方案:
方案一:用concurrent.futures.ProcessPoolExecutor(推荐,接口更简洁)
这个库的map方法可以直接处理多参数函数,而且自动帮你管理进程池,代码改动很小:
import numpy as np from concurrent.futures import ProcessPoolExecutor def fun(i1_val, i2_val, i3_val, i4_val): o1 = i1_val + i2_val + i3_val + i4_val o2 = i2_val*i3_val - i1_val + i4_val o3 = i1_val - i2_val + i3_val + i4_val if o1 < o2: o1 = o2 else: o2 = o1 while o1 + o2 > o3: o3 = o3 + np.random.random() return o1,o2,o3 if __name__ == "__main__": n = 1000 i1 = np.random.random(n) i2 = np.random.random(n) i3 = np.random.random(n) i4 = np.random.random(n) # 准备参数:把四个输入数组的对应元素打包成参数组 args_list = list(zip(i1, i2, i3, i4)) # 并行执行函数 with ProcessPoolExecutor() as executor: # 用*zip(*args_list)把参数组拆成四个独立的迭代器,对应fun的四个参数 results = list(executor.map(fun, *zip(*args_list))) # 把结果拆解到三个输出数组 o1, o2, o3 = np.array(list(zip(*results)))
关键步骤解释:
args_list = list(zip(i1, i2, i3, i4)):把四个输入数组的每个对应位置元素打包成元组,比如(i1[0], i2[0], i3[0], i4[0])就是第一个任务的参数。executor.map(fun, *zip(*args_list)):zip(*args_list)会把参数组重新拆解成四个包含所有对应位置元素的迭代器,*把它们作为单独的参数传给map,这样map就能正确地给fun传入四个参数。o1, o2, o3 = np.array(list(zip(*results))):results是一个包含1000个三元组的列表,zip(*results)会把所有三元组的第一个元素、第二个元素、第三个元素分别聚合起来,转成numpy数组就得到了最终的输出数组。
方案二:用multiprocessing.Pool
如果你习惯用更底层的multiprocessing库,也可以这样写,只需要稍微调整一下函数的参数格式:
import numpy as np from multiprocessing import Pool def fun(args): # 把传入的元组拆成四个参数 i1_val, i2_val, i3_val, i4_val = args o1 = i1_val + i2_val + i3_val + i4_val o2 = i2_val*i3_val - i1_val + i4_val o3 = i1_val - i2_val + i3_val + i4_val if o1 < o2: o1 = o2 else: o2 = o1 while o1 + o2 > o3: o3 = o3 + np.random.random() return o1,o2,o3 if __name__ == "__main__": n = 1000 i1 = np.random.random(n) i2 = np.random.random(n) i3 = np.random.random(n) i4 = np.random.random(n) with Pool() as pool: # 直接把参数组列表传给map,fun会自动接收每个元组 results = pool.map(fun, zip(i1, i2, i3, i4)) # 同样拆解结果到输出数组 o1, o2, o3 = np.array(list(zip(*results)))
注意事项:
- Windows系统必须加
if __name__ == "__main__"::这是因为Windows的多进程是通过重新导入脚本实现的,不加这个块会导致无限创建进程的问题。Linux/macOS虽然可以不用,但加上更稳妥。 - 随机数的差异:并行执行时,每个进程有自己的随机种子,所以最终的
o3结果和串行版本可能会有细微不同(因为串行是按顺序生成随机数,并行是多个进程同时生成)。如果需要严格可复现的结果,可以在fun里手动设置随机种子,比如np.random.seed(some_seed),不过一般不需要的话可以忽略。 - 性能考量:对于n=1000的规模,并行化的进程启动开销可能会抵消一部分收益,但如果n增大到几万甚至几十万,性能提升会非常明显。
内容的提问来源于stack exchange,提问作者Igor Dakic
相关产品推荐
相关产品推荐

