当任务属于CPU 密集型(大量计算、图像处理、模型训练等),Python 的多线程因 GIL 而无法充分利用多核,此时就需要多进程来真正并行执行。Python 的 multiprocessing 模块提供了与 threading 相似的 API,让你像写多线程一样写多进程。
multiprocessing 模块基础
每个进程都有独立的 Python 解释器和内存空间,因此进程间不共享全局变量,天然隔离。创建进程有两种典型方式:
1. 直接使用 Process 类
import multiprocessing
import os
def worker(name):
print(f"进程 {name} 运行中,PID: {os.getpid()}")
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()
要点:
- 必须将进程启动代码放在
if name == 'main':保护下,否则会在子进程中递归创建进程。 start()启动进程,join()等待进程结束。
2. 继承 Process 类
class MyProcess(multiprocessing.Process):
def run(self):
print(f"子进程 PID: {os.getpid()}")
p = MyProcess()
p.start()
p.join()
进程池 Pool
手动管理大量进程很麻烦,Pool 可以创建固定数量的工作进程,自动分配任务、收集结果,非常适合批量处理。
from multiprocessing import Pool
import time
def square(x):
time.sleep(1) # 模拟耗时操作
return x * x
if __name__ == '__main__':
with Pool(processes=4) as pool:
# 1. map:阻塞直到所有任务完成,返回结果列表
results = pool.map(square, range(10))
print(results)
# 2. apply_async:异步提交,可获取结果
async_result = pool.apply_async(square, (10,))
print(async_result.get()) # 阻塞等待
常用方法:
map(func, iterable):并行将iterable中每个元素代入func,返回结果列表,保持顺序。apply_async(func, args):异步提交一个任务,返回AsyncResult对象,可通过get()获取返回值。close()/join():禁止再提交新任务,等待所有子进程结束。用with语句会自动管理。
进程间通信 (IPC)
进程内存独立,想交换数据必须使用专门的通信机制。
1. Queue
基于管道和锁实现的线程/进程安全队列,生产者-消费者模型的首选。
from multiprocessing import Process, Queue
def producer(q):
for i in range(5):
q.put(i)
q.put(None) # 结束信号
def consumer(q):
while True:
item = q.get()
if item is None:
break
print(f"消费 {item}")
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start(); p2.start()
p1.join(); p2.join()
2. Pipe
返回一对连接对象,默认双向通信,适合两个进程间点对点通信。
from multiprocessing import Pipe, Process
def sender(conn):
conn.send("你好")
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=sender, args=(child_conn,))
p.start()
print(parent_conn.recv()) # 接收
p.join()
3. Value / Array(共享内存)
可以将某些数据类型放在共享内存中,让多个进程共同操作(需要显式加锁保证安全)。
from multiprocessing import Process, Value, Array
def f(n, a):
n.value = 3.1415926
for i in range(len(a)):
a[i] = -a[i]
if __name__ == '__main__':
num = Value('d', 0.0) # 'd' 表示 double
arr = Array('i', range(10)) # 'i' 表示 int
p = Process(target=f, args=(num, arr))
p.start(); p.join()
print(num.value) # 3.1415926
print(arr[:]) # [0, -1, -2, ...]
注意:Value / Array 操作不是原子性的,多进程同时写需要额外加 Lock。
实用建议
- CPU 密集型用多进程:矩阵运算、图像处理等能真正并行,进程数一般设为 CPU 核心数。
- IO 密集型优先考虑 asyncio 或线程池,多进程启动开销大,内存消耗高。
- Windows 特别注意:在 Windows 上子进程会从头导入整个模块,因此必须用
if name == 'main':保护启动代码,否则会陷入无限递归。 - 序列化限制:进程间传递的对象必须可 pickle 序列化,所以 lambda、非模块级函数等可能无法传递。尽量使用模块顶层定义的函数作为
target。 - 选择得当的通信方式:
Queue通用性好;Pipe高效但适用于一对一;Manager系列可创建在网络上共享的代理对象,但性能较低,适合控制结构而非高频数据交换。
掌握了多进程,你就可以把 Python 程序的计算部分扩展到全部 CPU 核心,真正突破 GIL 的性能瓶颈。