第 13 章 · 多进程
本章目标:理解进程与线程的区别;使用 multiprocessing 创建子进程与 ProcessPoolExecutor;掌握进程间通信 Queue / Pipe / Manager;能对 CPU 密集任务(批量计算、大 CSV 聚合)做并行加速;知道 if __name__ == "__main__" 在 Windows 上的必要性。
学时建议:4~5 小时(含 2 小时对比实验)
前置:ch12 多线程与锁(对比 GIL);ch08 文件读写;ch05 函数。
13.1 进程 vs 线程
| 对比项 | 线程 | 进程 |
|---|---|---|
| 内存 | 共享同一进程空间 | 独立地址空间 |
| 创建开销 | 较小 | 较大 |
| CPython CPU 并行 | 受 GIL 限制 | 真并行 |
| 通信 | 共享变量 + 锁 | Queue / Pipe / 共享内存 |
| 典型场景 | I/O 密集 | CPU 密集 |
┌────────────── 主进程 ──────────────┐
│ Python 解释器 + 你的代码 │
└───┬──────────┬──────────┬──────────┘
│ │ │
子进程 A 子进程 B 子进程 C ← 各自 GIL,互不干扰
shop-demo 场景:对 100 万行订单 CSV 做复杂统计、对商品列表批量算折扣哈希——适合进程池。
13.2 创建进程与 Windows 入口保护
Windows spawn 子进程时会重新 import 主模块,必须用:
from multiprocessing import Process
import os
def worker(name: str) -> None:
print(f"child {name} pid={os.getpid()}")
if __name__ == "__main__":
p = Process(target=worker, args=("A",))
p.start()
p.join()
print(f"parent pid={os.getpid()}")
| 要点 | 说明 |
|---|---|
if __name__ == "__main__" | Windows / macOS 默认 spawn 必须 |
target + args / kwargs | 同 Thread |
join() | 等待子进程结束 |
13.3 ProcessPoolExecutor:CPU 并行首选
与 ch12 ThreadPoolExecutor 同 API,换进程后端:
from concurrent.futures import ProcessPoolExecutor
import math
def is_prime(n: int) -> bool:
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
if __name__ == "__main__":
numbers = range(10_000, 20_000)
with ProcessPoolExecutor(max_workers=4) as pool:
primes = list(pool.map(is_prime, numbers))
print(sum(primes))
| 参数 | 建议 |
|---|---|
max_workers | 通常 = CPU 核数 或 核数 − 1 |
map | 保持输入顺序;适合同构任务 |
对比实验:用 ch12 线程池跑同一 is_prime,记录耗时——线程版几乎无提升。
13.4 进程间通信:Queue
子进程不能安全共享普通 list;用 multiprocessing.Queue:
from multiprocessing import Process, Queue
def producer(q: Queue) -> None:
for i in range(5):
q.put(f"order-{i}")
def consumer(q: Queue) -> None:
while True:
item = q.get()
if item is None:
break
print("got", item)
if __name__ == "__main__":
q: Queue = Queue()
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start()
p2.start()
p1.join()
q.put(None)
p2.join()