科学计算数据分析【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址https://gitcode.com/gh_mirrors/nu/numpy点击查看免费下载多线程环境下的正确使用是 NumPy 开发者绕不开的议题。本文基于官方文档 doc/source/reference/thread_safety.rst 展开系统梳理 NumPy 在标准库threading模块下的线程安全模型哪些操作会释放 GIL 从而获得并行收益、共享数组时存在哪些数据竞争风险、基于contextvars的上下文本地状态如何在多线程 / asyncio 场景下隔离配置以及 NumPy 2.1 起对 free-threaded Python无 GIL 运行时的实验性支持。读完本文你将掌握在 Python 线程中安全高效使用 NumPy 的边界条件与最佳实践并能理解其底层实现机制。多线程并行NumPy 为什么能突破 Python 的 GIL 限制CPython 的全局解释器锁GIL使得同一进程内多个线程无法真正并行执行 Python 字节码。但 NumPy 的核心数值运算都在 C 层实现大量低层操作会主动释放 GIL从而让多线程并行成为可能。这是 NumPy 与大多数纯 Python 代码在并发模型上的本质区别。文档明确指出thread_safety.rstMany NumPy operations release the GIL, so unlike many situations in Python, it is possible to improve parallel performance by exploiting multithreaded parallelism in Python.在源码层面释放 GIL 对应着一组贯穿整个_core的 C 宏。以数组赋值路径为例array_assign_array.c 和 array_assign_scalar.c 中普遍使用NPY_BEGIN_THREADS_DEF、NPY_BEGIN_THREADS_THRESHOLDED(nitems)与NPY_END_THREADS的组合当待处理元素个数nitems超过阈值时才真正释放 GIL兼顾了小数组的低开销与大数组的高并行度。类似模式也出现在归约、ufunc 计算等大量数值路径中如 calculation.c。最容易获得性能提升的线程模型官方推荐的并行策略非常简单让每个工作线程独占自己的数组或数组集合线程之间不共享数据。由于 NumPy 在低层代码中释放 GIL那些大部分时间停留在 C 层数值计算中的线程可以真正并行运行从而获得接近硬件核心数的加速比。import threading import numpy as np results [None] * 4 def worker(i): # 每个线程操作自己私有的数组无共享数据 x np.linspace(i, i 1, 1_000_000) results[i] np.sin(x).sum() threads [threading.Thread(targetworker, args(i,)) for i in range(4)] for t in threads: t.start() for t in threads: t.join() print(results)这种无共享数据模型能获得收益的前提是线程大部分时间停留在释放 GIL 的低层代码中若线程大量时间在 Python 层执行如小数组上的逐元素循环、频繁的 Python 回调并行收益会大打折扣。线程间共享数组可以但必须极度谨慎NumPy 数组对象本身可以跨线程共享但对共享数组的并发变更极易引发线程安全问题。官方文档给出两个层面的风险结果不一致且不可复现多个线程同时读写同一数组最坏情况下产生的是竞态racey、无法复现的结果解释器崩溃例如一个线程正在对数组做 ufunc 运算时另一个线程对同一数组执行resize等改变内存布局的操作可能直接导致 Python 解释器崩溃。因此文档给出的务实建议是共享数组时优先只读访问若必须并发变更则自行引入锁locking保护。NumPy 目前没有计划为numpy.ndarray内置锁机制文档注明in the future, we may add locking即未来可能加入但当下需要开发者自己负责同步。不释放 GIL 的操作何时应该改用 multiprocessing并非所有 NumPy 操作都会释放 GIL。文档特别强调对dtypenp.object_的数组执行的操作不会释放 GIL。这是因为 object 数组的元素是任意 Python 对象访问它们必须回到 Python 对象层无法在脱离 GIL 的情况下保证安全。这类操作使用threading模块看不到任何性能收益此时更适合改用标准库的multiprocessing模块进程级并行绕开 GIL。判断一个运算是否会释放 GIL可以从操作是否涉及 Python 对象回调入手纯数值类型int/float/complex 等的 ufunc、归约、赋值、广播等 → 释放 GILobject 数组上的操作、涉及__array_ufunc__/__array_function__协议回调的操作 → 通常不释放 GIL。上下文本地状态Context Local State多线程下的配置隔离NumPy 将用户可调整的配置项存储在 Python 标准库的context variablescontextvars模块中。这意味着配置状态具有上下文本地性多线程程序中的每个线程、asyncio 程序中的每个任务都拥有彼此独立的配置快照。官方文档列举了三类上下文本地状态状态类别对应配置相关文档浮点错误处理与 ufunc 缓冲numpy.errstate、numpy.setbufsizeroutines.err、use-of-internal-buffers打印与文本格式化numpy.printoptions及全部文本格式化选项text_formatting_options内存分配器数据分配策略NEP 49data_memory、NEP 49源码中的 contextvars 实现在源码中可以看到这些状态的真实载体打印选项独立定义在 numpy/_core/printoptions.py模块注释解释了为何要单独放置——多数组 C 模块在导入初始化时需要引用它放在arrayprint模块中会引入循环依赖。其默认值字典给出了各参数的出厂设置例如precision8、threshold1000、edgeitems3、linewidth75等用户通过np.printoptions修改的就是这个ContextVar。浮点错误处理与缓冲seterr、setbufsize均通过_extobj_contextvar.set(extobj)写入上下文见 numpy/_core/_ufunc_config.py 与 setbufsize 实现。注意 NumPy 2.0 起setbufsize的作用域已与errstate上下文绑定——退出with np.errstate():时缓冲大小也会被恢复。C 层面对应的 contextvar 由 extobj.c 的PyContextVar_New(numpy._legacy_resolver_promoting, ...)/npy_extobj_contextvar创建。内存分配器C API 的PyDataMem_SetHandler/PyDataMem_GetHandler使用PyContextVar_Get/PyContextVar_Set读写当前的分配策略处理器见 numpy/_core/src/multiarray/alloc.c。这使 NEP 49 提出的按上下文切换内存分配策略得以在无锁前提下线程安全地实现。用 with 语句设置上下文状态上下文变量的状态通过with语句语法设置。官方文档以打印精度为例 with np.printoptions(precision2): ... np.array([2.0]) / 3 array([0.67]) np.array([2.0]) / 3 array([0.66666667])with块内的配置只对当前上下文生效退出后自动恢复。这一性质适用于所有上下文本地状态而不只是printoptions——例如 import numpy as np with np.errstate(divideraise, invalidignore): ... print(np.geterr()) {divide: raise, over: warn, under: ignore, invalid: ignore} np.geterr() # 退出后恢复 {divide: warn, over: warn, under: ignore, invalid: warn}与 threading 模块的交互Python 3.14 前后的行为差异上下文变量在新创建的线程中如何初始化是线程安全的关键细节Python 3.14 之前新线程总是从全新的、初始化过的上下文状态开始主线程中通过with np.printoptions(...)设置的配置不会传导到子线程。官方示例$ python3.12 import numpy, threading def print_printoptions(): ... print(numpy.get_printoptions()[precision]) with numpy.printoptions(precision2): ... threading.Thread(targetprint_printoptions).start() 8即主线程精度为 2但子线程打印出的仍是默认精度 8。Python 3.14 起CPython 新增了thread_inherit_context启动配置项也可通过环境变量PYTHON_THREAD_INHERIT_CONTEXT启用。开启后新线程继承创建它的上下文的配置代码行为符合直觉——子线程在with块内启动时会看到精度 2$ python3.14 -Xthread_inherit_context1 import numpy, threading def print_printoptions(): ... print(numpy.get_printoptions()[precision]) with numpy.printoptions(precision2): ... threading.Thread(targetprint_printoptions).start() 2因此在 Python 3.14 之前当前大多数发行版默认如果需要在子线程中使用特定的 NumPy 配置最稳妥的做法是在子线程函数内部显式地用with语句设置配置而不是依赖主线程的上下文传导。Free-threaded Python无 GIL 运行时的实验性支持自NumPy 2.1起配合CPython 3.13NumPy 提供了对 free-threaded Python禁用 GIL 的运行时的实验性支持。官方说明文档标注versionadded:: 2.1。free-threaded 运行时没有 GIL 来串行化对 Python 对象的访问因此线程共享状态被并发变更的机会更多更易触发线程安全问题dtypenp.object_的数组不再受 GIL 保护——在常规 Python 中 object 数组由 GIL 隐式保护而在 free-threaded 环境下多线程读写 object 数组中的 Python 对象会产生常规环境不存在的数据竞争data races。此外free-threaded Python 在 3.14 版本起默认开启thread_inherit_context其上下文行为与上节描述一致见本文上下文本地状态一节。从源码看NumPy 为无 GIL 环境做了针对性适配例如 numpy/_core/src/common/npy_pycompat.h 中在Py_GIL_DISABLED编译条件下提供了基于 CPython 临界区critical section的NPY_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST宏用于安全调用PySequence_Fast等 API在非 free-threaded 构建下这些宏退化为空操作保证两套运行时使用同一份代码路径。需要说明的是该支持目前仍处于实验性阶段安装与使用 free-threaded Python 的具体方法、以及第三方库如何适配支持请参考官方相关文档与社区的 free-threading 指南。C-API 线程支持C 扩展开发者的参考对于编写 C 扩展并需要与 NumPy 交互的开发者NumPy 的 C-API 数组文档doc/source/reference/c-api/array.rst中提供了大量与多线程相关的细节包括何时可以安全释放 GILNPY_BEGIN_THREADS系列宏的使用规范数组内存布局在多线程读写下的约束与 Python 对象交互时必须重新获取 GIL 的场景。编写 C 扩展时应遵循与 Python 层相同的原则对共享 ndarray 的并发写操作需要自行同步纯数值循环可安全地使用NPY_BEGIN_THREADS释放 GIL 换取并行度。实战示例多线程随机数生成官方推荐的正确姿势官方文档的 See Also 一节指向了一个可直接落地的多线程实践doc/source/reference/random/multithreading.rst。它演示了如何安全地在多线程中填充数组核心思想是每个线程持有独立的Generator由SeedSequence派生各自写入预分配数组的不同切片线程间不共享可变状态from numpy.random import default_rng, SeedSequence import multiprocessing import concurrent.futures import numpy as np class MultithreadedRNG: def __init__(self, n, seedNone, threadsNone): if threads is None: threads multiprocessing.cpu_count() self.threads threads seq SeedSequence(seed) self._random_generators [default_rng(s) for s in seq.spawn(threads)] self.n n self.executor concurrent.futures.ThreadPoolExecutor(threads) self.values np.empty(n) self.step np.ceil(n / threads).astype(np.int_) def fill(self): def _fill(random_state, out, first, last): random_state.standard_normal(outout[first:last]) futures {} for i in range(self.threads): args (_fill, self._random_generators[i], self.values, i * self.step, (i 1) * self.step) futures[self.executor.submit(*args)] i concurrent.futures.wait(futures) def __del__(self): self.executor.shutdown(False)使用方式In [2]: mrng MultithreadedRNG(10000000, seed12345) ...: print(mrng.values[-1]) Out[2]: 0.0 In [3]: mrng.fill() ...: print(mrng.values[-1]) Out[3]: 2.4545724517479104该模式的关键点在于预分配数组np.empty(n)只创建一次out参数让各核心分布函数直接填充既有连续、可写、对齐的数组避免每次调用重新分配的开销切片隔离每个线程只写values[first:last]自己的区间互不重叠从根本上规避数据竞争可复现性同一seed、相同线程数下结果完全可复现线程数变化会改变切片边界输出随之变化长生命周期线程ThreadPoolExecutor中的线程长期复用避免频繁建线程的额外开销。这也从实践角度印证了本文核心原则线程私有状态 只读/分区访问共享数组是 NumPy 多线程编程最安全高效的组合。小结NumPy 多线程编程的准则清单利用 GIL 释放特性让线程的大部分时间停留在 C 层数值计算中即可获得真实并行收益优先采用每线程独立数组模型确需共享数组时只读访问或自行加锁严禁在另一线程读取/计算数组时对其执行resize等改变内存布局的操作dtypenp.object_的操作不释放 GIL多线程场景下应考虑multiprocessing依赖上下文本地状态errstate、printoptions、内存分配策略时在 Python 3.14 之前需在子线程内部自行设置配置在 free-threaded Python 下NumPy 2.1 提供实验性支持但 object 数组存在新的数据竞争风险务必额外加锁C 扩展开发者参考 C-API 数组文档 中关于 GIL 释放与多线程的规范。赞分享科学计算数据分析【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址https://gitcode.com/gh_mirrors/nu/numpy点击查看免费下载相关推荐Perspective Python 多线程编程指南线程安全 API、GIL 释放与并发性能调优Perspective Python 多线程编程指南线程安全 API、GIL 释放与并发性能调优 Perspective 是一个面向大规模与流式数据集的交互式数据可视化数据分析流处理WebAssembly图表库macOS 上的 PythonCPython官方安装器、free-threaded 构建与开发实践全指南macOS 上的 PythonCPython官方安装器、free threaded 构建与开发实践全指南 本文基于 CPython 仓库中的官方文档 Do编程语言语言运行时解释器标准库CPython C API 线程编程参考GIL、线程状态PyThreadState与线程附着/分离全指南CPython C API 线程编程参考GIL、线程状态PyThreadState与线程附着/分离全指南 本文基于 CPython 官方文档 线程状态与全编程语言语言运行时解释器标准库上一篇Candle免费开源的GRBL控制器终极指南快速掌握CNC机床可视化控制下一篇Mobile Boilerplate项目CSS指南构建移动优先的样式基础创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考