人人都会AI编程

16.3 多进程:multiprocessing 模块、进程池、进程间通信

更新时间:2026-07-12

当任务属于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 的性能瓶颈。