Python 多进程与进程池
多进程绕过 GIL 做 CPU 并行;注意序列化、内存与启动方式,优先进程池管理生命周期。
#type / concept
#status / growing
#tech / dev
#resource / python
#tech / lang / python
[!info] 关联笔记
Python 多进程与进程池
这个概念为什么出现
纯 Python CPU 密集(压缩、哈希爆破、图像像素变换)在多线程下难吃满多核。多进程每个进程独立解释器与 GIL,可真正并行,但要付序列化传参与内存成本。
[!abstract] 一句话理解 进程池把 CPU 任务分到多进程执行以绕过 GIL;参数/返回值需可 pickle,适合可分割的计算型负载。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:批量为文件块计算校验和(CPU 模拟)
# process_pool_hash.py
# 业务意图:并行计算多块数据的哈希摘要(用计数模拟 CPU 工作)。
# 教学点:
# - ProcessPoolExecutor;
# - 顶层可 pickle 函数;
# - if __name__ 保护(Windows/spawn)。
from concurrent.futures import ProcessPoolExecutor
from time import perf_counter
def crunch(n: int) -> int:
# 模拟 CPU:累加
total = 0
for i in range(n):
total += i % 7
return total
def main() -> None:
work = [300000] * 4
t0 = perf_counter()
with ProcessPoolExecutor(max_workers=4) as pool:
results = list(pool.map(crunch, work))
print("results:", results)
print("elapsed≈", round(perf_counter() - t0, 3))
if __name__ == "__main__":
main()
建议运行:
python process_pool_hash.py
期望输出(elapsed 随机器变化):
results: [899994, 899994, 899994, 899994]
elapsed≈ 0.xxx
结合场景再看三个关注点
- 函数必须可被子进程导入——定义在顶层。
if __name__ == "__main__"防止 spawn 递归。- 任务太小会被进程开销吃掉收益。
核心概念与准确模型
multiprocessing/ProcessPoolExecutor- 启动方式:fork/spawn/forkserver
- 队列、共享内存进阶 API
- 与线程池接口相似(futures)
边界情况与反直觉行为
- lambda/闭包难 pickle。
- 大对象传输成为瓶颈。
- 全局状态不共享(除显式 IPC)。
常见误区
[!warning] 常见误区:所有变慢都上多进程 错误理解:进程越多越快。
正确模型:先确认 CPU 瓶颈与任务粒度。
工程实践
- 池大小≈CPU 核数(容器限额内)
- 批处理合并小任务
- 失败重试与超时
本节总结
多进程是 CPython CPU 并行主路径之一;把它当分布式计算的缩小版管理。
自测题
- 为什么需要
if __name__保护? - 为何小任务可能不加速?
参考答案
- spawn 会再导入主模块,无保护会重复启动。
- 进程启动与序列化开销大于计算本身。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| multiprocessing | 文档 | 进程 |
| ProcessPoolExecutor | 文档 | 进程池 |