Python 多进程之间如何通信?Queue、Pipe、Manager 和 shared_memory 有什么区别?
简化版
多进程默认内存隔离,不能像线程那样直接共享普通变量。常见通信方式有 multiprocessing.Queue 传消息,Pipe 做两个端点通信,Manager 提供代理对象,shared_memory 用于共享大块内存。一般任务分发优先 Queue,大数据共享才考虑 shared memory。
详细版
对比:
| 方式 | 特点 | 适合场景 |
|---|---|---|
Queue | 多生产者多消费者消息队列 | 任务分发、结果回传 |
Pipe | 两端连接,点对点通信 | 父子进程简单对话 |
Manager | 通过代理共享 list、dict 等对象 | 简单共享状态,性能要求不高 |
shared_memory | 共享原始内存块 | 大数组、大块二进制数据 |
Queue 示例:
from multiprocessing import Process, Queue
def worker(q):
q.put("done")
if __name__ == "__main__":
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get())
p.join()
注意点:
- 跨进程传递对象通常涉及序列化;
- 不要频繁传超大对象;
- Manager 简单但开销较大;
- shared memory 性能更好,但需要自己管理结构和同步;
- 进程退出时要注意资源释放。
完整版教学
一、多进程为什么需要专门通信机制
线程共享同一进程内存,所以可以直接访问同一个对象。进程不同,每个进程通常有独立地址空间:
进程 A 的变量 x 和进程 B 的变量 x 不是同一个内存位置
这带来隔离性,也带来通信成本。要让进程协作,就必须使用 IPC,也就是进程间通信机制。
二、Queue 是最常用的任务通道
multiprocessing.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(item)
它适合:
- 父进程给子进程派任务;
- 子进程把结果返回父进程;
- 多个 worker 之间传递简单消息。
但传入 Queue 的对象通常要序列化,所以不要频繁传巨大的 DataFrame、图片数组或模型对象。
三、Pipe 更像一根双向管道
Pipe 返回两个连接端:
from multiprocessing import Pipe
parent_conn, child_conn = Pipe()
它适合两个进程之间点对点通信。相比 Queue,它语义更轻,但不如 Queue 适合多生产者多消费者模型。
如果通信拓扑简单,比如父进程发命令、子进程回结果,Pipe 可以很直观。
四、Manager 简单但不适合高性能
Manager 可以创建看起来像共享的字典、列表:
from multiprocessing import Manager
with Manager() as manager:
shared_dict = manager.dict()
但这些对象通常是代理,操作会经过管理进程协调,不是普通内存直接访问。它方便,但慢。
适合:
- 少量共享状态;
- 原型代码;
- 管理配置、状态标记。
不适合高频读写、大数据传输和性能敏感路径。
五、shared_memory 用于共享大块数据
如果多个进程要处理同一大块数组,复制成本很高,可以考虑共享内存。共享内存让多个进程访问同一块底层内存,减少复制。
但它更底层:
- 需要自己定义数据结构;
- 要管理生命周期;
- 要处理同步;
- 更容易出资源泄漏或数据竞争。
所以面试时可以说:普通任务通信优先 Queue,性能瓶颈明确且数据很大时才考虑 shared memory。
六、进程通信的设计原则
实践中有几个原则:
- 传小消息,不传大对象;
- 能让 worker 自己加载资源,就不要每次传资源;
- 明确结束信号;
- 主进程负责收集结果和异常;
- 对共享内存和 Manager 状态加同步保护。
这比单纯背 API 更重要。
七、常见误区与追问
| 方式 | 适合场景 | 主要代价 |
|---|---|---|
Queue | 多生产者多消费者任务流 | 数据需要序列化和跨进程传输 |
Pipe | 两端点直接通信 | 拓扑简单,扩展性弱 |
Manager | 共享字典、列表等代理对象 | 代理进程转发,性能较低 |
shared_memory | 大数组、大块字节共享 | 需要自己管理生命周期和同步 |
- 误区:多进程之间能像多线程一样直接共享变量。 进程默认地址空间隔离,普通全局变量在不同进程里是各自副本,不是同一个对象。
- 误区:Manager 对象和普通 dict/list 性能一样。
Manager通过代理进程协调访问,方便但有 IPC 开销,不适合高频小操作。 - 误区:shared_memory 共享了数据就不需要同步。 它只共享内存块,不自动保护读写顺序;多个进程同时写仍需要锁、队列或协议约束。
- 追问:Queue 和 Pipe 怎么选? 多生产者多消费者、任务分发用
Queue更自然;固定两个端点点对点通信可用Pipe。 - 追问:为什么进程通信常要求对象可序列化? 因为跨进程传递通常要把对象编码成字节再发送到另一端,不能序列化的对象无法安全复制过去。
- 追问:大数据传输为什么不总用 Queue? 大数组反复序列化和复制成本很高,
shared_memory可以减少复制,但要额外处理同步和释放。
记忆钩子:进程通信先问“传消息、共享代理,还是共享大块内存”,不要把所有问题都塞进
Manager。
八、加强记忆
多进程默认不共享普通内存,所以要靠 IPC 协作。Queue 是最通用的消息通道,Pipe 是点对点管道,Manager 是方便但偏慢的代理共享状态,shared_memory 是高性能但更底层的大数据共享方案。先用 Queue,真遇到大对象复制瓶颈再升级。