multiprocessing 是 Python 标准库的多进程模块,用进程替代线程绕过 GIL 限制实现真正并行;跨平台与嵌套场景首选 spawn 启动方式。
官方文档:https://docs.python.org/3/library/multiprocessing.html
核心概念
- 进程:独立运行环境,拥有独立内存空间
- GIL:全局解释器锁,同一时刻只允许一个线程执行字节码
- multiprocessing:使用进程替代线程,实现真正并行
基本用法
创建进程
import multiprocessing
def worker(name):
print(f"Worker {name} running")
if __name__ == "__main__":
p1 = multiprocessing.Process(target=worker, args=("A",))
p2 = multiprocessing.Process(target=worker, args=("B",))
p1.start()
p2.start()
p1.join()
p2.join()进程池
from multiprocessing import Pool
def square(x):
return x * x
if __name__ == "__main__":
with Pool(4) as p:
results = p.map(square, [1, 2, 3, 4, 5])
print(results) # [1, 4, 9, 16, 25]进程间通信
# 队列
queue = multiprocessing.Queue()
queue.put(item)
item = queue.get()
# 共享内存
counter = multiprocessing.Value('i', 0) # 整数
arr = multiprocessing.Array('i', [1, 2, 3]) # 数组共享内存示例
def increment(counter):
for _ in range(1000):
counter.value += 1
if __name__ == "__main__":
counter = multiprocessing.Value('i', 0)
processes = [multiprocessing.Process(target=increment, args=(counter,)) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
print(f"Counter: {counter.value}")启动方式:fork vs spawn
spawn 是跨平台的「干净启动」,fork 是 Unix 的「快速复制」。
| 维度 | fork(Unix 专属) | spawn(跨平台) |
|---|---|---|
| 创建机制 | 复制父进程地址空间(COW) | 启动新解释器,重新导入脚本 |
| 启动速度 | 极快 | 较慢 |
| 资源继承 | 继承所有资源 | 仅继承必要资源 |
| 线程安全 | 父进程有活跃线程时易死锁 | 天然线程安全 |
| 嵌套进程 | 易出问题 | 完美支持 |
| 跨平台 | 差(Windows 不支持) | 好(全平台通用) |
原理:
- fork:调用
fork()复制父进程整个地址空间,子进程从fork()位置开始执行;写时复制(COW),修改数据时才真正拷贝内存 - spawn:启动全新 Python 解释器,子进程重新导入当前脚本,因此必须用
if __name__ == '__main__':保护主逻辑
实践验证:全局变量的差异
fork 模式子进程直接继承父进程的全局变量;spawn 模式子进程重新执行模块顶层代码,全局变量回到初始状态:
import multiprocessing
GLOBAL_VAR = "父进程初始化"
def worker():
print(f"子进程: {GLOBAL_VAR}")
if __name__ == '__main__':
# fork:继承父进程的全局变量
multiprocessing.set_start_method('fork')
p = multiprocessing.Process(target=worker)
p.start(); p.join()
# spawn:重新初始化全局变量
multiprocessing.set_start_method('spawn', force=True)
p = multiprocessing.Process(target=worker)
p.start(); p.join()嵌套进程
子进程里可以再启动子进程。关键三点:
- 主进程设置
multiprocessing.set_start_method(),重复调用会报错,放脚本开头或用force=True - 用
if __name__ == '__main__':保护入口 - Windows 必须用
spawn;fork 模式嵌套大概率死锁,spawn 稳定运行
import multiprocessing
import time
def nested_worker(num):
print(f"嵌套进程 {num} PID: {multiprocessing.current_process().pid}")
time.sleep(1)
def parent_worker(pid):
print(f"父进程 {pid} PID: {multiprocessing.current_process().pid}")
processes = []
for i in range(2):
p = multiprocessing.Process(target=nested_worker, args=(f"{pid}-{i}",))
processes.append(p)
p.start()
for p in processes:
p.join()
if __name__ == '__main__':
multiprocessing.set_start_method('spawn', force=True)
main_processes = []
for i in range(2):
p = multiprocessing.Process(target=parent_worker, args=(i,))
main_processes.append(p)
p.start()
for p in main_processes:
p.join()嵌套进程用 join() 确保生命周期可控,层级限制在 2-3 层,并用 Pool 限制并发数。
避坑指南
| 问题 | 原因 | 解决 |
|---|---|---|
| Windows 下 RuntimeError | 未加 if __name__ == '__main__': 保护 | 所有进程启动逻辑放保护块内 |
| fork 模式死锁 | 父进程有活跃线程或嵌套进程 | 改用 spawn |
| spawn 启动慢 | 每次新建解释器 | 用进程池复用 |
set_start_method 报错 | 重复调用 | 放脚本开头,或用 force=True |
| 资源耗尽 | 嵌套层级过多 | 限制 2-3 层,使用 Pool |
| 进程间通信错误 | 直接共享变量 | 使用 Queue/Pipe/Value/Array |
此外:
- 进程创建开销比线程大,适合 CPU 密集型任务;I/O 密集型优先考虑 asyncio
- 进程间共享数据必须走 Queue、Value、Array 等原语
选择建议
| 场景 | 推荐 |
|---|---|
| 纯 Linux/macOS、无嵌套、追求性能 | fork |
| 跨平台、嵌套进程、有活跃线程 | spawn |
| 数据处理、进程池 | spawn |
| 简单脚本 | spawn(减少适配成本) |