
本文介绍如何通过 multiprocessing.Array 和内存映射机制,在多进程并行调用中实现对同一 NumPy 数组的原地写入(in-place modification),解决因进程隔离导致的参数修改无效问题。
本文介绍如何通过 multiprocessing.array 和内存映射机制,在多进程并行调用中实现对同一 numpy 数组的原地写入(in-place modification),解决因进程隔离导致的参数修改无效问题。
在 Python 中,标准的多进程并行方案(如 multiprocessing.Pool 或 joblib.Parallel)默认采用进程间数据拷贝(fork 或 pickle 传输),这意味着函数内部对数组的修改仅作用于子进程的本地副本,父进程中的原始数组不会被更新。要实现真正的“共享内存式”并行写入,必须绕过序列化传递,转而使用操作系统级共享内存对象。
核心思路是:将 NumPy 数组底层缓冲区(buffer)映射到一个 multiprocessing.Array 实例中,使所有子进程访问同一块物理内存。为此,我们封装两个关键工具函数:
-
np_array_to_shared_array():将初始 NumPy 数组内容复制到共享内存块; -
shared_array_to_np_array():基于该共享内存块重建一个“视图型” NumPy 数组(零拷贝),供各子进程直接操作。
此外,需借助 Pool 的 initializer 机制,在每个子进程启动时,将共享内存映射为全局变量(如 a),确保所有工作函数可一致访问。
以下是完整、可运行的示例代码:
立即学习“Python免费学习笔记(深入)”;
import numpy as np
import multiprocessing
def np_array_to_shared_array(np_array: np.ndarray, lock: bool = True) -> multiprocessing.Array:
"""将 NumPy 数组内容初始化至 multiprocessing.Array 共享内存块"""
shared_array = multiprocessing.Array('B', np_array.nbytes, lock=lock)
buffer = shared_array.get_obj() if lock else shared_array
arr = np.frombuffer(buffer, dtype=np_array.dtype)
arr[:] = np_array.flatten(order='C')
return shared_array
def shared_array_to_np_array(shared_array: multiprocessing.Array,
shape: tuple[int],
dtype: object) -> np.ndarray:
"""从 multiprocessing.Array 构建共享内存视图的 NumPy 数组"""
buffer = shared_array.get_obj() if hasattr(shared_array, 'get_obj') else shared_array
return np.ndarray(shape, dtype=dtype, buffer=buffer)
def init_pool(shared_array, shape, dtype):
global a
a = shared_array_to_np_array(shared_array, shape, dtype)
def test(i):
length = a.shape[0]
if i % 2 == 0:
for x in range(0, length, 2):
a[x] = 2
else:
for x in range(1, length, 2):
a[x] = 1
def main():
# 初始化原始数组
arr = np.zeros(10, dtype=np.uint8)
shape, dtype = arr.shape, arr.dtype
# 创建共享内存并映射为 NumPy 视图
shared_array = np_array_to_shared_array(arr, lock=False)
# 注意:此处 arr 已被重新绑定为共享视图(非必需,仅为验证一致性)
arr = shared_array_to_np_array(shared_array, shape, dtype)
# 启动双进程池,通过 initializer 注入共享数组
with multiprocessing.Pool(
processes=2,
initializer=init_pool,
initargs=(shared_array, shape, dtype)
) as pool:
pool.map(test, [0, 1])
print(arr) # 输出: [2 1 2 1 2 1 2 1 2 1]
if __name__ == '__main__':
main()✅ 关键注意事项:
-
数据类型匹配:
multiprocessing.Array类型码(如'B'表示uint8)必须与 NumPydtype严格一致,否则内存解释错误; -
锁(lock)选择:若多个进程并发写入不同索引位置(如本例中偶/奇下标分离),可设
lock=False提升性能;若存在竞争写入同一位置,应启用lock=True并在写入前加锁; -
避免 pickle 传递数组:切勿将
a作为参数传入starmap或delayed—— 这会触发拷贝,破坏共享语义; -
Windows/macOS 兼容性:该方案在 Windows(spawn 启动方式)下同样有效,因
initializer在子进程启动时执行,不依赖 fork; -
替代方案提示:对更复杂场景(如动态形状、多维切片、结构化数组),可考虑
shared_memory模块(Python 3.8+)或numba.cuda/mpi4py等专业并行库。
综上,通过显式管理共享内存生命周期 + initializer 全局注入,即可在保持代码简洁性的同时,安全、高效地实现多进程对同一 NumPy 数组的协同修改。


















