因为默认用pickle序列化大数组会触发全量内存拷贝和cpu密集型编码,导致进程启动慢、内存翻倍甚至oom;根本解法是改用shared_memory零拷贝共享或numpy.memmap文件映射。

为什么不能直接用 multiprocessing.Pool 传大数组?
因为默认使用 pickle 序列化,超大 numpy.ndarray 会触发内存拷贝 + 高开销序列化,进程启动慢、内存翻倍甚至 OOM。你看到的卡顿或 BrokenPipeError / PicklingError 很可能就源于此。
根本解法是避免传递数组本体,改用共享内存或只传切片索引。
- 小数组(Pool.map 加
copy=False(仅限只读)勉强可行,但不推荐 - 中大数组(100MB–10GB):优先走
multiprocessing.shared_memory(Python 3.8+) - 超大数组(>10GB)或需跨 Python 版本:用
numpy.memmap文件映射更稳
用 shared_memory 实现零拷贝切分
核心思路:主进程创建共享内存块 → 把数组数据写入其中 → 子进程通过名称 attach 进来 → 各自按索引切片视图(np.ndarray 的 __array_interface__ 可复用)。
注意:shared_memory 不管理数据类型和形状,这些必须显式传给子进程。
import numpy as np from multiprocessing import shared_memory, Process import multiprocessing as mp <h1>假设原始数组很大</h1><p>arr = np.random.rand(100000000).astype(np.float32) # ~400MB</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3219" title="python全能编程助手"><img src="https://img.php.cn/upload/skill/000/000/081/178952049933674.jpg" alt="python全能编程助手" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill3219" title="python全能编程助手" class="overflowclass">python全能编程助手</a> <p class="overflowclass">SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、</p> </div> <a rel="nofollow" href="/xiazai/skill3219" title="python全能编程助手" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div><h1>创建共享内存</h1><p>shm = shared_memory.SharedMemory(create=True, size=arr.nbytes) shared_arr = np.ndarray(arr.shape, dtype=arr.dtype, buffer=shm.buf) shared_arr[:] = arr[:] # 复制数据进去</p><h1>切分逻辑:按行/按块索引,不复制数据</h1><p>def worker(shm_name, shape, dtype, start_idx, end_idx): existing_shm = shared_memory.SharedMemory(name=shm_name)</p><h1>构造只读视图(关键!避免意外修改)</h1><pre class="brush:python;toolbar:false;">view = np.ndarray(shape, dtype=dtype, buffer=existing_shm.buf) chunk = view[start_idx:end_idx] # 真·切片,无拷贝 result = np.sum(chunk) # 示例计算 existing_shm.close() return result
启动多进程(这里用 4 个)
procs = [] with mp.Pool(4) as pool: chunk_size = len(arr) // 4 args_list = [ (shm.name, arr.shape, arr.dtype, i chunk_size, (i + 1) chunk_size) for i in range(4) ] results = pool.starmap(worker, args_list)
print("sums:", results) shm.close() shm.unlink() # 必须手动清理
memmap 更兼容、适合超长生命周期任务
当数组太大无法全载入内存,或需在多个不相关进程间反复访问时,numpy.memmap 是更鲁棒的选择。它把数组映射到磁盘文件,OS 负责页调度,进程间天然共享且不依赖 Python 版本。
- 写入一次,所有进程可并发读(无需锁)
- 切分只需传
(filename, dtype, shape, offset, length),子进程自己np.memmap(..., offset=..., shape=...) - 注意:不要用
mode='r+'并发写,会损坏数据;写操作请单进程完成
示例片段:
# 主进程:保存为 memmap 文件 filename = "/tmp/large_array.dat" arr.tofile(filename) # 或用 np.memmap 创建时指定 mode='w+' <h1>子进程函数</h1><p>def memmap_worker(filename, dtype, shape, offset, length): chunk = np.memmap(filename, dtype=dtype, mode='r', offset=offset, shape=(length,)) return np.mean(chunk)</p><h1>计算每个 chunk 的 offset(按字节)</h1><p>itemsize = np.dtype(dtype).itemsize offsets = [i <em> chunk_size </em> itemsize for i in range(4)] args_list = [(filename, arr.dtype, arr.shape, off, chunk_size) for off in offsets]</p>
容易被忽略的三个硬伤
实际跑起来才发现问题?大概率栽在这几个点上:
-
shared_memory在 macOS 上有已知 bug(Python unlink() 可能失败导致后续运行报FileExistsError;建议加try/except+ 手动rm /dev/shm/xxx - Windows 下
fork不可用,必须用spawn启动方式,意味着全局变量不会自动继承——所有共享参数(如shm.name、shape)必须显式传入子进程,不能靠闭包 - 切片后若做
.copy()或触发广播运算(如chunk + scalar),仍会分配新内存;务必用np.ndarray.flags.c_contiguous和.data.ptr检查是否真共享
共享内存不是银弹,切分逻辑写错一行,就退回拷贝地狱。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










