结论:multiprocessing.pool能加速cpu密集型任务,但对i/o密集型任务效果有限甚至更慢;根本原因在于进程创建、pickle序列化/反序列化及ipc开销常远超小任务本身耗时,且任务须可序列化、避免gil无关的资源竞争。

直接说结论:用 multiprocessing.Pool 能有效加速 CPU 密集型任务,但对 I/O 密集型任务效果有限,甚至可能更慢;关键不是“用了池就快”,而是任务类型、数据序列化开销和进程间通信方式共同决定实际收益。
为什么 Pool.map() 有时比单进程还慢?
常见错误是把小计算量、高序列化成本的任务丢进池里——比如处理几百个短字符串的简单判断。每次调用 map() 都要把参数打包成 pickle、跨进程传输、反序列化、执行、再传回结果。这个过程本身就有几毫秒开销。
- 任务函数不能是嵌套定义或 lambda(会 pickle 失败,报
AttributeError: Can't pickle local object) - 所有参数和返回值必须可被
pickle序列化;numpy.ndarray可以,但带自定义方法的类实例通常不行 - 默认启动
os.cpu_count()个进程,但若任务含锁、文件读写或全局状态,反而因竞争变慢
怎样正确传参给 Pool.map()?
map() 只接受单参数函数,但多数实际任务需要多个参数。别写循环调用 apply_async(),也别硬塞元组再解包——推荐用 functools.partial 或封装预设参数的 wrapper 函数。
- 错误写法:
pool.map(func, [(a1,b1), (a2,b2)])→func得自己拆元组,且无法利用参数自动分片 - 正确做法:
from functools import partial; pool.map(partial(func, extra_arg=42), arg_list) - 若需不同参数组合,用
pool.starmap(func, [(a1,b1), (a2,b2)])——注意这是 Python 3.3+ 才支持
什么时候该用 maxtasksperchild?
长期运行的进程池容易因内存泄漏或累积状态变慢,尤其在使用第三方 C 扩展(如某些 cv2 或数据库驱动)时。设置 maxtasksperchild 让每个子进程只处理固定数量任务后自动重启。
- 默认值为
None(永不死),生产环境建议显式设为100–1000,视任务内存增长情况调整 - 配合
initializer参数可让每个新进程初始化一次资源(如打开数据库连接),避免重复开销 - 注意:重启进程有额外开销,别设得太小(如
1),否则得不偿失
真正卡住人的地方往往不是语法,而是没意识到子进程无法共享父进程的内存地址、日志配置、或未关闭的文件句柄——这些不会报错,但会让程序行为诡异或缓慢。调试时先确认任务是否真的 CPU-bound,再看 pickle 日志有没有警告,最后才调优进程数和分块策略。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











