multiprocessing.Pool 在 Python 3.11 下可绕过 GIL 并行 CPU 密集任务,提速达 3–3.5×,但仅当任务计算量大、避免 I/O/锁、合理分块且控制序列化开销时才优于单进程。

为什么 multiprocessing.Pool 比单进程快,但又不是总快
Python 3.11 的 Pool 能绕过 GIL,把 CPU 密集型任务(如正则匹配、分词、TF-IDF 向量化)真正并行到多个核上。但前提是:任务本身不能被 I/O 或锁卡住,且子进程开销不压倒收益。比如对 100 万行文本做 re.findall,单进程可能卡在内存和解释器调度上;而用 Pool 切成 4 块并发处理,实测提速常达 3–3.5×(非线性,受 CPU 核数与数据局部性影响)。但若每行只做 len() 这种微操作,Pool 反而更慢——序列化参数、进程间通信的开销盖过了计算收益。
用 Pool.map 处理大文件时必须逐行读取 + 分块,别 load 全文
直接 open(file).read() 加载几百 MB 文本会吃光内存,Pool 子进程各自再复制一份,雪上加霜。正确做法是主进程按固定行数切片(例如每 5000 行为一块),用 itertools.islice 流式生成 chunk,再丢给 Pool.map。这样每个子进程只加载当前 chunk,内存可控。
- 别传文件路径让子进程自己 open —— 文件句柄无法跨进程传递,会报
OSError: [Errno 9] Bad file descriptor - chunk 数据类型用
list[str],避免传入含闭包或模块级对象的函数(否则子进程 import 失败) - 正则编译放在子进程函数内部(或用
functools.partial预绑定已编译的re.Pattern),避免跨进程序列化 pattern 对象
Python 3.11 下要注意 Pool 的启动方式与异常捕获
在 Windows 和某些 macOS 环境下,if __name__ == '__main__' 保护仍是硬性要求;但在 Python 3.11+ Linux 上,若用 spawn 启动方法(默认),仍需该守卫,否则子进程反复执行主模块代码导致无限 fork。更关键的是:子进程中未捕获的异常默认只打印 traceback 到子进程 stderr,主进程 map 返回时静默失败。必须显式用 try/except 包裹 worker 函数,并返回带错误信息的元组,例如:
def extract_features(lines):
try:
urls = []
for line in lines:
# 使用预编译的 url_pattern
for m in url_pattern.finditer(line):
urls.append(m.group(0))
return {"urls": urls, "error": None}
except Exception as e:
return {"urls": [], "error": str(e)}
主进程拿到结果后检查 result["error"] 字段,而非依赖 map 抛异常。
临时文件和共享状态必须显式管理,别依赖全局变量
子进程有独立内存空间,写入的 tempfile.NamedTemporaryFile 或修改的全局 list/dict 不会反映到主进程。需要中间落盘时,用 tempfile.mkstemp(dir="/dev/shm")(Linux 下挂载为内存盘)加速 IO;需要聚合结果时,改用 multiprocessing.Manager().list() 或返回值收集。另外,Pool 默认使用 spawn 方法(Python 3.11+),意味着每次新建子进程都要重新 import 所有模块——所以把 heavy import(如 sklearn.feature_extraction.text)移到 worker 函数内,避免主进程启动慢。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











