字数 2229,阅读大约需 12 分钟
Python3.14:自由线程,No GIL, 提升执行性能
学习自由线程的文章
1. 更快的 Python:解开 Python 全局解释器锁 | The PyCharm Blog[1]
2. Python 对自由线程的支持 — Python 3.14.0 文档[2]
3. 并发性能提升 200%+ !Python no-GIL 实验版本性能实测并发性能提升 200%+ !Python no- - 掘金[3]
说明:与启用默认全局解释器锁的构建相比,自由线程构建在执行 Python 代码时有额外的开销,官方目标是,与启用默认全局解释器锁的构建相比,pyperformance 套件的开销不超过 10%。
识别自由线程
要判断当前解释器是否支持自由线程,可检查 python -VV 和 sys.version 是否包含 "free-threading build"。 新的 sys._is_gil_enabled() 函数可用于检查在运行进程中 GIL 是否确实被关闭。
sysconfig.get_config_var("Py_GIL_DISABLED") 配置变量可用于确定构建是否支持自由线程 。 如果该变量设置为 1,则构建支持自由线程。 这是与构建配置相关的决策的推荐机制。
Python 的全局解释器锁(GIL)
"全局解释器锁"(Global Interpreter Lock,简称 GIL)是 Python 社区中的常见术语,也是一个众所周知的 Python 特性。那么,GIL 到底是什么呢?
如果你有使用其他编程语言(例如 Rust)的经验,你可能已经知道什么是互斥锁(mutex)。互斥锁可以确保数据每次只能由一个线程访问,防止数据被多个线程同时修改。你可以把它视为一种"锁",它会阻止所有线程访问数据,除了持有密钥的线程之外。
GIL 本质上就是一个互斥锁。它一次只允许一个线程访问 Python 解释器。我有时把它想象成 Python 的方向盘——你肯定不会想让多个人同时操控方向盘!但话说回来,一群人旅行时经常会换司机,这就有点像把解释器访问权限交给另一个线程。
由于 GIL 的存在,Python 不允许真正的多线程处理。这项功能在过去十年中引发了争议,有很多尝试通过移除 GIL 来提高 Python 的速度。最近在 Python 3.13 中,引入了一种无需 GIL 即可使用 Python 的选项,有时也称为"无 GIL"或"自由线程 Python"。由此,Python 编程的新时代开始了。
为什么 GIL 最开始会出现?
既然 GIL 这么不受欢迎,那当初为什么要实现它呢?拥有 GIL 其实有很多好处。
在其他具有真正多线程处理的编程语言中,有时问题源自多个线程修改数据,最终结果取决于哪个线程或进程首先完成。这被称为**"竞争条件"**。像 Rust 这样的语言通常很难学习,因为程序员必须使用互斥锁防止竞争条件。
在 Python 中,所有对象都有一个引用计数器来跟踪有多少其他对象需要从它们获取信息。如果引用计数器达到零,因为我们知道由于 GIL,Python 中不存在竞争条件,我们可以放心地声明该对象不再需要并且可以作为垃圾被回收。
历史背景:当 Python 在 1991 年首次发布时,大多数个人计算机只有一个核心,并且没有多少程序员要求多线程处理支持。拥有 GIL 可以解决程序实现中的很多问题,同时还使代码易于维护。因此,Python 的创造者 Guido van Rossum 于 1992 年添加了 GIL。
快进到 2025 年:个人计算机拥有多核处理器,因此计算能力更强。我们可以利用额外的算力实现真正的并发。
Python 中的多进程处理
在深入探究移除 GIL 的流程之前,我们先看看 Python 开发者如何使用 multiprocessing 库实现真正的并发。
multiprocessing 标准库提供本地和远程并发,使用子进程而不是线程有效避开全局解释器锁。这样一来,multiprocessing 模块允许程序员充分利用特定机器上的多个处理器。
multiprocessing 示例
下面是一个使用 multiprocessing 的简单示例:
import multiprocessing
import time
def make_burger(order_num):
print(f"Preparing burger #{order_num}...")
time.sleep(5) # 制作汉堡的时间
print(f"Burger made #{order_num}")
if __name__ == "__main__":
print("Number of available CPU:", multiprocessing.cpu_count())
s = time.perf_counter()
all_processes = []
for i in range(3):
process = multiprocessing.Process(target=make_burger, args=(i,))
process.start()
all_processes.append(process)
for process in all_processes:
process.join()
elapsed = time.perf_counter() - s
print(f"Orders completed in {elapsed:0.2f} seconds.")multiprocessing 的限制
使用 multiprocessing 时存在一些限制:
1. pickle 序列化限制:multiprocessing 使用 pickle,它通常只能序列化顶层模块函数。嵌套函数会导致错误。
2. 不能使用全局变量共享数据:要在不同进程之间共享数据,必须使用特殊的类对象(如
Queue和Value)并作为实参传递给进程。3. 需要使用 Lock 避免竞争条件:当多个进程访问共享数据时,必须使用
Lock来确保数据完整性。
lock.acquire()
try:
current_num = order_num.value
order_num.value = current_num + 1
finally:
lock.release()移除 GIL 的技术挑战
近十年来,移除 GIL 一直是热门话题:
• 2016 年:Larry Hastings 在 Python Language Summit 上讲解了他对 CPython 解释器进行"GIL 切除"的想法
• 2021 年:Sam Gross 重新引发了关于移除 GIL 的讨论
• 2023 年:发布了 PEP 703 - Making the Global Interpreter Lock Optional in CPython
可以看到,移除 GIL 绝不是一个仓促的决定,已经在社区内引起很多讨论和争论。
1. 引用计数的挑战
有 GIL 时:可以依靠简单的非原子引用计数,当引用计数达到零时移除对象。
无 GIL 时:需要使用偏向引用计数(Biased Reference Counting)来保证线程安全:
• 将每个对象偏向于所有者线程(大多数时间访问该对象的线程)
• 所有者线程可以对其拥有的对象执行非原子引用计数
• 其他线程则需要对这些对象执行原子引用计数
• 这个方法比单纯的原子引用计数更高效
此外,一些常用的 Python 对象(如 True、False、小整数和一些暂存字符串)都是持久的,不需要引用计数。
2. 垃圾回收的改进
无 GIL 环境下,垃圾回收使用了**"延迟引用计数"**技术:
• 需要减少引用计数时,对象被存储在一个表中
• 表后续将接受双重检查以确定引用计数的递减是否准确
• 这避免了在对象被引用时过早移除对象
关键要求:垃圾回收期间引用计数需要保持稳定,因此在垃圾回收周期中,必须"停止整个世界"来提供线程安全保证。
3. 内存分配器的更新
有 GIL 时使用 Python 内部内存分配器 pymalloc。
无 GIL 时采用 mimalloc(由 Microsoft 维护的通用分配器):
• 线程安全
• 在小对象上性能良好
• 使用页面填充堆,使用块填充页面
• 每个页面内的块大小均相同
性能测试:有无 GIL 的对比
测试环境设置
可以使用 pyenv 安装两个版本:
• 标准版(例如 3.13.5)
• 自由线程版(例如 3.13.5t)
测试结果
带 GIL 的标准版本 3.13.5:
CPU count: 8
Range size: 1000000
Number of chunks: 16
Running single-threaded version (baseline)...
Found 78498 primes in 1.1930 seconds
Running multi-threaded version with 4 threads...
Found 78498 primes in 1.2183 seconds (speedup: 0.98x)
SUMMARY:
Threads Time (s) Speedup
1 1.1930 1.00x
4 1.2183 0.98x自由线程版本 3.13.5t(无 GIL):
CPU count: 8
Range size: 1000000
Number of chunks: 16
Running single-threaded version (baseline)...
Found 78498 primes in 1.5869 seconds
Running multi-threaded version with 4 threads...
Found 78498 primes in 0.4662 seconds (speedup: 3.40x)
SUMMARY:
Threads Time (s) Speedup
1 1.5869 1.00x
4 0.4662 3.40x结论:自由线程版本在多线程环境下实现了 3.4 倍的速度提升!
未来展望
截至 2025 年,Python 的自由线程版本仍然不是默认版本。不过,随着社区和第三方库的采用,Python 的自由线程版本预计将在未来成为标准。
根据公布的消息,Python 3.14 将包含一个自由线程版本,该版本将度过实验阶段,但仍为可选。
关于 PyCharm
PyCharm 提供了一流的 Python 支持,可以确保速度和准确性。受益于最智能的代码补全、PEP 8 合规性检查、智能重构和各种检查,满足所有编码需求。
如本文所示,PyCharm 为 Python 解释器和运行配置提供了自定义设置,只需点击几下即可在解释器之间切换,这使其适用于各种 Python 项目。
本文整理自 JetBrains PyCharm 官方博客
引用链接
[1] 更快的 Python:解开 Python 全局解释器锁 | The PyCharm Blog: https://blog.jetbrains.com/zh-hans/pycharm/2025/08/faster-python-unlocking-the-python-global-interpreter-lock/[2] Python 对自由线程的支持 — Python 3.14.0 文档: https://docs.python.org/zh-cn/3.14/howto/free-threading-python.html[3] 并发性能提升 200%+ !Python no-GIL 实验版本性能实测并发性能提升 200%+ !Python no- - 掘金: https://juejin.cn/post/7484589584719609895
评论区