线程安全#
NumPy 支持通过标准库中的 threading 模块在多线程环境中进行使用。许多 NumPy 操作会释放 GIL,因此与 Python 中的许多情况不同,通过利用 Python 的多线程并行性可以提高并行性能。
最容易实现的性能提升发生在每个工作线程拥有自己的数组或一组数组对象时,线程之间没有直接的数据共享。因为 NumPy 在许多底层操作中会释放 GIL,所以花费大部分时间在底层代码中的线程将并行运行。
在线程之间共享 NumPy 数组是可能的,但必须非常小心,以避免在修改共享的数组时产生线程安全问题。如果两个线程同时读取和写入同一个数组,最坏的情况下会产生不一致、不可重现的竞争结果,更不用说正确性了。例如,当另一个线程正在读取一个数组以计算 ufunc 操作时,同时修改该数组的大小也可能导致 Python 解释器崩溃。
未来,我们可能会为 ndarray 添加锁定机制,以使使用 NumPy 数组编写多线程算法更加安全,但目前我们建议专注于对线程之间共享的数组进行只读访问,或者在需要修改和多线程时添加自己的锁定。
请注意,*不* 会释放 GIL 的操作将无法从使用 threading 模块中获得性能提升,而使用 multiprocessing 可能会更好。特别是,对具有 dtype=np.object_ 的数组的操作不会释放 GIL。
上下文本地状态#
NumPy 在 ufuncs 的上下文中维护一些状态,这意味着多线程程序中的每个线程或 asyncio 程序中的每个任务都有其独立的 numpy.errstate(参见 浮点数错误处理)和 文本格式化选项 的配置。
您可以通过进入上下文管理器来更新存储在上下文变量中的状态。一旦退出上下文管理器,状态将重置为进入上下文管理器之前的值。
无 GIL 线程 Python#
2.1 版本新增。
从 NumPy 2.1 和 CPython 3.13 开始,NumPy 还对禁用 GIL 的 Python 运行时提供了实验性支持。有关安装和使用 无 GIL 线程 Python 的更多信息,以及有关支持依赖于 NumPy 的库的信息,请参阅 https://py-free-threading.github.io。
由于无 GIL 线程 Python 没有全局解释器锁来序列化对 Python 对象的访问,因此线程有更多机会修改共享状态并产生线程安全问题。除了上面关于 ndarray 对象锁定的限制外,这也意味着具有 dtype=np.object_ 的数组不受 GIL 的保护,从而产生在无 GIL 线程 Python 之外不可能出现的数据竞争。
C-API 线程支持#
对于编写与 NumPy 交互的 C 扩展的开发者来说,C-API 数组文档 的多个部分提供了关于多线程注意事项的详细信息。
另请参阅#
多线程生成 - 在多线程环境中使用 NumPy 的随机数生成器的实际示例,使用了
concurrent.futures。