必须用multiprocessing.Array或RawArray共享NumPy数组,普通ndarray传参只是副本;真正共享后需用Lock保护读-改-写操作,否则出现静默竞态错误。

共享NumPy数组必须用multiprocessing.Array或RawArray,不能直接传ndarray
Python多进程默认不共享内存,你看到的“共享数组”其实是子进程各自拷贝了一份副本。如果误用全局变量或模块级ndarray,各进程操作的完全是不同内存地址的数据,根本谈不上锁冲突——而是压根没共享成功。真正触发锁冲突的场景,是显式使用multiprocessing.Array或multiprocessing.RawArray映射同一块共享内存后,多个进程同时读写它。
np.ndarray本身不带进程锁,所有同步得靠你自己加
NumPy的ndarray对象只是对内存块的封装,它不内置任何进程同步机制。当你把multiprocessing.Array转成np.ndarray(比如用np.frombuffer()),得到的只是一个指向共享内存的视图,读写操作仍会直接落在底层共享内存上。此时:
- 没有自动锁:
a[0] = 1和a[0] += 1都不是原子操作,可能被进程调度打断 - 竞态真实存在:两个进程同时执行
a[0] += 1,可能都读到旧值、都加1、都写回,结果只+1而非+2 - 不会报错但结果错:这类冲突通常不抛异常,只导致计算结果静默错误
用multiprocessing.Lock保护共享数组访问区域
最稳妥的做法是把对共享数组的读-改-写逻辑包在同一个Lock临界区内:
import multiprocessing as mp
import numpy as np
<p>def worker(shared_arr, lock, start_idx, length):</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts"><img
src="https://img.php.cn/upload/skill/000/000/081/179231049778675.jpg" alt="361SCAN security check your skill python scripts" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts">361SCAN security check your skill python scripts</a>
<p>扫描并分析已安装技能,用于查看指定技能目录的名称、描述和详情,或列出所有已安装的技能。</p>
</div>
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/00968c3c2c15" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">Python免费学习笔记(深入)</a>”;</p><h1>转为numpy视图(注意dtype和shape必须匹配)</h1><pre class="brush:php;toolbar:false;">arr = np.frombuffer(shared_arr.get_obj(), dtype=np.float64).reshape(-1)
with lock:
for i in range(start_idx, start_idx + length):
arr[i] = i ** 2 # 所有修改都在锁内完成if name == 'main': size = 1000 shared_arr = mp.Array('d', size) # 'd'表示double lock = mp.Lock() p1 = mp.Process(target=worker, args=(shared_arr, lock, 0, 500)) p2 = mp.Process(target=worker, args=(shared_arr, lock, 500, 500)) p1.start(); p2.start() p1.join(); p2.join()
关键点:
-
mp.Array和mp.RawArray才真正共享内存;普通ndarray传参只是序列化再反序列化 -
np.frombuffer()返回的视图必须与原始Array的dtype严格一致,否则内存越界或数值错乱 - 锁粒度别太细:不要为单个元素加锁,而应围绕业务逻辑块(如一次批量赋值)加锁
避免用Value或Array存大数组,优先考虑文件或内存映射
当数组超过几MB时,mp.Array的构造和np.frombuffer()转换开销明显,且容易因锁争用拖慢整体性能。更实际的替代方案:
- 用
mmap创建内存映射文件,各进程独立打开同一映射,配合fcntl.flock()做区域锁 - 用
joblib.Memory缓存中间数组到磁盘,进程间通过路径通信而非内存共享 - 真需要高频共享,改用
ray或dask.distributed,它们在底层做了更精细的内存管理和零拷贝传输
最常被忽略的一点:你以为在“共享数组”,其实90%的情况是根本没共享成功——先确认id(np.frombuffer(...))在不同进程中是否一致,再谈锁不锁。

















