SciPy 中的线程安全性#
SciPy 支持通过标准库中的 threading 模块在多线程环境中使用。许多 SciPy 操作会释放全局解释器锁(GIL),NumPy 也是如此(且许多 SciPy 功能是以调用 NumPy 函数实现的)——因此与 Python 中的许多情况不同,通过利用 Python 中的多线程并行性,可以提高并行性能。
当每个工作线程拥有其自己的数组或数组对象集,且线程间没有直接共享数据时,最容易获得性能提升。大部分时间在底层代码中运行的线程通常会并行执行。
在线程间共享 NumPy 数组是可能的,但在修改在多个线程间共享的数组时,必须格外小心以避免产生线程安全问题——请参阅 NumPy 关于线程安全性的文档了解更多详情。除非函数明确记录其会这样做(这种情况很少见),否则 SciPy 函数不会修改用户传入的数组。因此,在同一个 NumPy 数组上以线程方式调用 SciPy 函数是安全的。
虽然 SciPy 大部分由函数组成,但在使用类和数据结构时需要更加小心。
具有状态的类,例如 scipy.integrate 和 scipy.interpolate 中的一些积分和插值对象,通常在并行调用时具有鲁棒性。它们要么接受并行调用,要么抛出提示性错误。例如,对于不支持并行执行的积分方法,scipy.integrate.ode 可能会抛出 IntegratorConcurrencyError。
scipy.spatial 提供的 KDTree 也是线程安全的,因为在创建后没有用于修改 KDTree 的接口。从多个线程同时查询 KDTree 是安全的。
SciPy 提供了一些数据结构,即 scipy.sparse 中的稀疏数组和矩阵,它们是可变(mutable)的。这些数据结构目前不是线程安全的。特别是在数据跨多个线程共享时,请避免进行修改数据结构的操作,例如在稀疏数组上使用项(item)或切片(slice)赋值。这可能会导致数据损坏、崩溃或其他不希望出现的行为。如果您必须支持这些对象的共享修改,请注意同步访问或确保不存在共享修改的可能性。有关处理 Python 中线程不安全对象的方法,请参阅 Python 自由线程指南中关于 写时复制(copy-on-write) 和 锁(locks) 的部分以获取更多指导。
请注意,不会释放 GIL 的操作在使用 threading 模块时不会获得性能提升,反而可能更适合使用 multiprocessing。然而,请参阅下文关于 SciPy 对自由线程构建支持的详细信息,该构建没有此限制。
自由线程 Python(Free-threaded Python)#
版本 1.15.0 中新增。
从 SciPy 1.15.0 和 CPython 3.13 开始,SciPy 对在所有平台上禁用 GIL 的 Python 运行时提供了实验性支持。有关安装和使用自由线程 Python 的更多信息,请参阅 https://py-free-threading.github.io。
由于自由线程 Python 没有用于序列化 Python 对象访问的全局解释器锁(GIL),因此线程修改共享状态并产生线程安全问题的机会更多。所有 SciPy 功能都经过了并行线程使用的测试,但我们预计仍会有尚未发现的问题——如果您遇到问题,请检查 GitHub 上带有 free-threading 标签的问题,如果该异常函数尚未有相关 issue,请打开一个新的 issue。