菜单

自由线程与并发

相关源文件

本页面将介绍 Python 的并发方法,特别关注 Python 3.13 中引入的实验性 free-threading 模式。有关多进程和其他并发执行方法的信息,请参阅 Python 的标准库并发模块。

介绍

CPython,Python 的参考实现,传统上使用全局解释器锁 (GIL) 来同步对 Python 对象的访问。此锁阻止多个原生线程同时执行 Python 字节码。虽然 GIL 简化了内存管理并防止了竞态条件,但它也限制了使用基于线程的并行性来利用多个 CPU 核心的能力。

Python 3.13 引入了一个实验性的“free-threading”模式,允许 Python 在没有 GIL 的情况下运行,从而可以跨多个 CPU 核心真正并行执行 Python 线程。

来源: Doc/whatsnew/3.13.rst306-352

全局解释器锁 (GIL)

什么是 GIL?

GIL 是一个互斥锁,可确保一次只有一个线程在 Python 解释器中执行 Python 字节码。当一个线程想要执行 Python 代码时,它必须先获取 GIL。如果另一个线程已经持有 GIL,则请求的线程必须等待 GIL 被释放。

GIL 实现

GIL 在 CPython 中通过 _gil_runtime_state 结构实现,该结构包含

  • locked:一个原子整数标志,指示 GIL 当前是否被持有
  • gil_mutex:一个用于控制对 GIL 访问的互斥锁
  • gil_cond:一个用于线程等待 GIL 的条件变量
  • last_holder:指向最后一个持有 GIL 的线程状态的指针

线程切换机制

为防止单个线程垄断 GIL,Python 实现了一种协作式线程切换机制

  1. 在执行一段时间(默认 5 毫秒)后,线程可以释放 GIL。
  2. 另一个等待的线程可以设置 gil_drop_request 标志
  3. 当前线程会定期检查此标志,并在收到请求时释放 GIL。
  4. 其他线程有机会获取 GIL。

来源: Python/ceval_gil.c49-144 Python/ceval_gil.c202-275

Python 3.13 中的 Free-Threading 模式

概述

Python 3.13 引入了基于 PEP 703 的实验性无 GIL 运行支持。这使得多个线程可以在不同的 CPU 核心上并行执行 Python 代码,从而可能提高 CPU 密集型多线程代码的性能。

启用 Free-Threading

Free-threading 模式需要特殊的构建配置

  1. 使用预编译的“free-threaded”Python 可执行文件(通常命名为 python3.13tpython3.13t.exe
  2. 或者使用 --disable-gil 选项从源代码构建 Python。

即使使用 free-threaded 构建,您也可以在运行时控制 GIL。

  • 设置环境变量 PYTHON_GIL=1 来启用 GIL。
  • 使用命令行选项 -X gil=1 来启用 GIL。
  • 使用 sys._is_gil_enabled() 检查当前进程中是否启用了 GIL。

来源: Doc/whatsnew/3.13.rst306-347 Python/pylifecycle.c543-574

Free-Threading 的内存管理更改

为了支持 free-threading,CPython 的内存管理进行了重大更改。

  1. 有偏引用计数:对象同时具有线程局部 (ob_ref_local) 和共享原子 (ob_ref_shared) 引用计数。
  2. 每个对象的锁:每个对象都有自己的互斥锁 (ob_mutex) 用于同步。
  3. 线程特定内存管理:线程局部内存区域,以减少争用。

来源: Include/object.h152-163

线程状态和解释器状态

状态之间的关系

CPython 的并发模型围绕几个关键结构构建。

  • 运行时状态 (_PyRuntimeState):整个 Python 进程的全局状态。
  • 解释器状态 (PyInterpreterState):Python 解释器实例的状态。
  • 线程状态 (PyThreadState):执行 Python 代码的线程的状态。

线程状态结构

PyThreadState 结构保存线程特定的数据。

  • 指向解释器状态的指针 (interp)
  • 当前执行帧 (current_frame)
  • 状态标志 (_status),包括 initializedboundactive
  • GIL 状态 (holds_gil)
  • 求值中断标志 (eval_breaker)

在 free-threading 模式下,还会使用额外的线程管理状态。

  • _Py_THREAD_ATTACHED:线程正在运行 Python 代码。
  • _Py_THREAD_DETACHED:线程未运行 Python 代码。
  • _Py_THREAD_SUSPENDED:线程已暂停(例如,用于垃圾回收)。

来源: Python/pystate.c44-107 Include/internal/pycore_pystate.h43-47

扩展模块兼容性

使扩展兼容 Free-Threading

C 扩展模块需要显式指示其与 free-threading 的兼容性。

  1. 对于多阶段初始化,请使用 Py_mod_gil 插槽。

  2. 对于单阶段初始化,请使用 PyUnstable_Module_SetGIL

对导入扩展的影响

导入 C 扩展时:

  • 如果扩展指示了 free-threading 支持,则它将在没有 GIL 的情况下运行。
  • 如果未支持,则整个进程将启用 GIL(除非设置了覆盖标志)。
  • 在 free-threaded 构建中安装扩展需要 pip 24.1+。

来源: Doc/whatsnew/3.13.rst332-341

性能考量

权衡

Free-threading 模式提供了性能上的权衡。

  • 好处:CPU 密集型多线程代码可以通过真正的并行性运行得更快。
  • 成本:单线程代码可能因同步开销而运行得更慢。
  • 成本:由于额外的线程局部数据结构,内存使用量更高。
  • 警告:由于是实验性的,可能存在错误和稳定性问题。

最佳实践

在考虑 free-threading 模式时:

  1. 基准测试您的特定工作负载,以查看其是否受益。
  2. 彻底测试是否存在竞态条件和线程问题。
  3. 验证兼容性与所需的 C 扩展模块。
  4. 考虑替代方案,例如在 free-threading 更稳定之前,为生产工作负载使用多进程。

来源: Doc/whatsnew/3.13.rst318-325

检查和管理 GIL 状态

要有效地使用 free-threading 模式:

  1. 检查构建支持python -VV 将显示“experimental free-threading build”。
  2. 检查运行时 GIL 状态sys._is_gil_enabled() 返回 GIL 是否已启用。
  3. 控制启动时的 GIL:
    • 环境变量:PYTHON_GIL=1(启用)或 PYTHON_GIL=0(禁用)。
    • 命令行:-X gil=1(启用)或 -X gil=0(禁用)。

来源: Doc/whatsnew/3.13.rst327-331

未来方向

Free-threading 模式在 Python 3.13 中是实验性的,并且很可能在未来的版本中发展。关键的改进领域包括:

  • 单线程代码的性能优化。
  • 第三方 C 扩展的更好支持。
  • 针对并行代码的增强调试工具。
  • 改进的文档和最佳实践。

虽然对 CPU 密集型应用程序前景看好,但在经过彻底测试之前,不建议在生产环境中使用 free-threading。

来源: Doc/whatsnew/3.13.rst346-352