查看: 111|回复: 3

Python进程间通信三种方法Queue Manager SharedM

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
笔者上个月处理200万条用户行为日志,单线程清洗要13秒。心想8核服务器用多进程应该飞快,于是写了如下代码:
  1. import multiprocessing as mp
  2. CONFIG = {"date_format": "%Y-%m-%d", "max_len": 100}
  3. results = []
  4. def process_line(line):
  5.     cleaned = do_clean(line, CONFIG)
  6.     results.append(cleaned)
  7.     return cleaned
  8. if __name__ == "__main__":
  9.     with mp.Pool(4) as pool:
  10.         pool.map(process_line, data)
  11.     print(len(results))  # 输出0!
复制代码

运行后主进程的results依然是空列表。原因很简单:每个子进程拥有独立的内存空间,主进程定义的results在子进程中是拷贝,修改不影响主进程。Python多进程间通信必须显式传递数据,下面介绍三种正确姿势。

---

## 一、Queue:传送带式通信

multiprocessing.Queue内部处理了同步问题,多个进程可以安全地put和get。适用于生产者-消费者模式、任务分发和结果收集。

### 经典用法
  1. from multiprocessing import Process, Queue
  2. def worker(q):
  3.     while True:
  4.         item = q.get()
  5.         if item is None:  # 结束信号
  6.             break
  7.         result = item * 2
  8.         q.put(result)
  9. if __name__ == "__main__":
  10.     q = Queue()
  11.     p = Process(target=worker, args=(q,))
  12.     p.start()
  13.     for i in range(10):
  14.         q.put(i)
  15.     q.put(None)  # 通知子进程结束
  16.     p.join()
  17.     while not q.empty():
  18.         print(q.get())
复制代码

关键点:put()和get()默认阻塞,队列满时put等待,队列空时get等待。常用None作为结束信号。适合任务数量不确定、动态分发的场景。性能中等,需要序列化。

---

## 二、Manager:共享复杂数据结构

Manager启动一个独立的管理器进程,通过代理对象实现共享。操作共享列表、字典就像操作普通对象,但每次读写都有进程间通信开销。

### 示例
  1. from multiprocessing import Process, Manager
  2. def worker(shared_list, shared_dict, value):
  3.     shared_list.append(value)
  4.     shared_dict[value] = value * 2
  5. if __name__ == "__main__":
  6.     with Manager() as manager:
  7.         shared_list = manager.list(["初始数据"])
  8.         shared_dict = manager.dict()
  9.         processes = []
  10.         for i in range(5):
  11.             p = Process(target=worker, args=(shared_list, shared_dict, i))
  12.             processes.append(p)
  13.             p.start()
  14.         for p in processes:
  15.             p.join()
  16.         print(list(shared_list))  # ['初始数据', 0, 1, 2, 3, 4]
  17.         print(dict(shared_dict))  # {0: 0, 1: 2, 2: 4, 3: 6, 4: 8}
复制代码

支持list、dict、Namespace、Lock、Queue等。数据量不大、需要共享复杂容器时使用。注意:每次操作都有IPC开销,性能比直接内存访问慢。

---

## 三、SharedMemory:零拷贝内存共享

SharedMemory让多个进程直接读写同一块物理内存,无需序列化,速度最快。Python 3.8+内置。适合大数据量、高性能计算,尤其是NumPy数组共享。

### 基础用法
  1. from multiprocessing import shared_memory
  2. import numpy as np
  3. # 主进程创建共享内存
  4. shm = shared_memory.SharedMemory(create=True, size=1024)
  5. arr = np.ndarray((256,), dtype=np.int32, buffer=shm.buf)
  6. arr[:] = np.arange(256)
  7. # 子进程通过shm.name连接同一块内存
  8. # shm_b = shared_memory.SharedMemory(name=shm.name)
  9. # arr_b = np.ndarray((256,), dtype=np.int32, buffer=shm_b.buf)
  10. # 修改arr_b即修改arr
  11. shm.close()
  12. shm.unlink()  # 必须释放
复制代码

### 使用SharedMemoryManager自动管理生命周期
  1. from multiprocessing.managers import SharedMemoryManager
  2. with SharedMemoryManager() as smm:
  3.     sl = smm.ShareableList(range(2000))
  4.     # 多个进程共享sl
  5.     # 退出with自动释放所有共享内存
复制代码

性能极高(零拷贝),但需要开发者管理内存分配和释放。适合MB级以上的数据频繁读写。

---

## 决策原则

| 通信方式 | 适用场景 | 性能 | 易用性 |
|---------|---------|------|-------|
| Queue | 任务分发、结果收集 | 中等(需序列化) | ★★★★★ |
| Manager | 共享列表/字典等复杂结构 | 较慢(协调开销) | ★★★★ |
| SharedMemory | 大数组、高性能需求 | 极快(零拷贝) | ★★★(需手动管理) |

一句话选择:只传递任务和结果选Queue;需要共享容器、数据量不大选Manager;共享大数组、追求极致性能选SharedMemory。

回到开头的数据处理任务,最终改用Queue方案:主进程将200万条日志分批放入Queue,4个子进程轮流取数据处理,结果放入另一个Queue,主进程收集。耗时从13秒降到3.6秒,再也不会出现results为0的尴尬。

多进程内存隔离是Python并发编程的常见陷阱。掌握Queue、Manager、SharedMemory三种通信方式,就能让数据在进程间安全、高效地流通。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python进程间通信三种方法Queue Manager SharedM

很好的经验分享!那个results为空的例子太真实了,新手很容易栽在这上面。表格总结得非常清晰,我平时多用Manager共享字典来存配置,没想到SharedMemory性能这么高,以后处理大日志文件可以试试这个零拷贝方案。感谢楼主整理!
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python进程间通信三种方法Queue Manager SharedM

楼主这帖子太实用了!之前用多进程也踩过 `results` 空列表的坑,看完才知道是子进程独立内存空间的问题。三种方法对比得很清楚,Queue 适合动态任务分发,Manager 操作简单但开销大,SharedMemory 零拷贝高性能,决策表也一目了然。想问下楼主实际处理那200万条数据时,最终选了哪种方案?分别试过对比性能吗?
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python进程间通信三种方法Queue Manager SharedM

感谢楼主的详细总结!之前我也踩过 `results` 在子进程里修改主进程看不到的坑,看完这篇终于理清楚了。 想请教一下:在实际使用 `SharedMemory` 时,如果多个子进程同时读写同一块区域,是否需要额外加锁?文档里提到 `ShareableList` 本身不是线程安全的,但多进程场景下怎么保证数据一致性呢?另外,楼主提到 `Manager` 性能较慢,那在数据量中等(比如几十万条字符串)时,用 `Queue` 收集结果和用 `Manager.list` 收集,实际差距大概有多大?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-7 12:32 , Processed in 0.035798 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部