在编程的世界中,并发是提高应用程序性能的关键因素。在 Python 中,全局解释器锁(GIL)一直是实现真正并发的主要阻碍。然而,随着即将发布的 Python 3.13,GIL 变成了可选的!这一实验性特性标志着 Python 多线程能力演进的重要一步。
在这篇文章中,我们将深入了解GIL是什么,它如何影响你的代码,以及最重要的是,如何利用Python 3.13中的这个新功能来加速你的多线程应用程序性能。
zh: GIL是什么呢?
GIL 是由 CPython 解释器用来确保任何时候只有一个线程在执行 Python 字节码的全局解释器锁。这意味着,即使你同时运行了多个线程,也只会有一个线程在执行 Python 代码。虽然这对于早期版本的 Python 来说,防止数据损坏和确保行为的一致性是必要的,但在现代应用程序中,其中并发性至关重要,这已成为现代应用程序中一个关键的瓶颈。
Python 3.13 新增了哪些功能?
Python 3.13,引入了一个实验性功能,允许你彻底关闭 GIL!这意味着线程可以更平行运行,对于某些类型的任务负载,这可能带来显著的性能提升。
为了利用这一新功能,你需要下载并安装Python 3.13的测试版(rc1,即发布候选版1),它包含了一个免费线程构建配置。然后,你可以通过环境变量或命令行参数来开启或关闭GIL。
使用可选GIL的好处
在你的 Python 应用中实现真正的并行处理可以带来深远的好处,包括:
- 提高性能:通过允许多个线程同时运行,你就可以为计算密集型任务实现显著的速度加快。
- 提高可扩展性:通过同时运行更多线程,你就可以处理更大的负载并更有效地扩展应用程序。
如何在Python 3.13中检测并禁用GIL[^1]
[^1]: GIL,即全局解释器锁(global interpreter lock)
具有实验性功能的 CPython 允许通过在多个核心上并行运行线程来充分利用处理能力。为线程设计的程序将在多核处理器上运行得更快。
要启用多线程构建,你需要具体来说,要么使用 --disable-gil
选项编译自己的解释器,要么安装带有自由线程标识的实验性构建。请注意,这项功能尚处于开发阶段,可能包含一些错误,并且可能对单线程性能有较大影响。
用户可以使用环境变量 PYTHON_GIL 或命令行选项 -X gil 在运行时可选地启用 GIL,这样做。
Python 代码
这段Python代码旨在衡量禁用全局解释器锁(GIL)并在并行计算中使用多线程的有效性。具体来说,它使用六个并发线程来计算相同的六个数的阶乘。
#!/usr/bin/env python3
# 导入所需模块
import sys
import sysconfig
import math
import time
from threading import Thread
from multiprocessing import Process
# 定义一个装饰器来测量函数执行时间
def time_taken(func):
"""
一个装饰器,用于测量函数的执行时间。
参数:
func: 目标函数。
返回:
一个包装函数,测量并打印执行时间。
"""
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
execution_time = end_time - start_time
print(f"函数 {func.__name__!r} 执行了 {execution_time:.4f} 秒。")
return result
return wrapper
# 定义一个计算密集型任务作业函数
def compute_intensive_task(num):
"""
计算密集型任务作业,计算数字的阶乘值。
参数:
num: 输入数字。
返回:
输入数字的阶乘值。
"""
#return math.sqrt(num) # 注释掉此行以专注于多线程
return math.factorial(num)
# 定义单线程任务函数
@time_taken
def single_threaded_task(nums):
"""
单线程任务函数,顺序执行计算密集型作业。
参数:
nums: 输入数字列表。
"""
for num in nums:
compute_intensive_task(num)
# 定义多线程任务函数
@time_taken
def multi_threaded_task(nums):
"""
多线程任务函数,创建和运行线程池以执行任务。
参数:
nums: 输入数字列表。
"""
threads = []
# 创建 len(nums) 个线程
for num in nums:
thread = Thread(target=compute_intensive_task, args=(num,))
threads.append(thread)
thread.start()
# 等待所有线程结束
for thread in threads:
thread.join()
# 定义多进程任务函数
@time_taken
def multi_processing_task(nums):
"""
多进程任务函数,创建和运行进程池以执行任务。
参数:
nums: 输入数字列表。
"""
processes = []
# 创建 len(nums) 个进程
for num in nums:
process = Process(target=compute_intensive_task, args=(num,))
processes.append(process)
process.start()
# 等待所有进程结束
for process in processes:
process.join()
# 定义程序的主函数
def main():
"""
程序的主函数,打印 Python 版本并检查 GIL 是否已启用。然后运行单线程、多线程和多进程任务函数。
"""
print(f"Python 版本: {sys.version}")
# 检查 GIL 状态
py_version = float(".".join(sys.version.split()[0].split(".")[0:2]))
status = sysconfig.get_config_var("Py_GIL_DISABLED")
if py_version >= 3.13:
status = sys._is_gil_enabled()
if status is None:
print("GIL 无法在 Python 3.12 及更早版本中禁用")
if status == 0:
print("GIL 当前已禁用")
if status == 1:
print("GIL 当前正在启用")
nums = [300001, 300001, 300001, 300001, 300001, 300001]
# 运行单线程任务函数
single_threaded_task(nums)
# 运行多线程任务函数
multi_threaded_task(nums)
# 运行多进程任务函数
multi_processing_task(nums)
# 调用主函数
if __name__ == "__main__":
main()
示例运行和分析
我们这里有三组数据:
PYTHON_GIL=0
(GIL 关闭) 使用 Python 3.13-rc 版本PYTHON_GIL=1
(GIL 开启) 使用 Python 3.13-rc 版本PYTHON_GIL=1
(GIL 开启) 使用 Python 3.12 版本
## PYTHON_GIL=0 (禁用 GIL)使用 Python 3.13-rc
$ PYTHON_GIL=0 ./gil_test2.py
Python 版本:3.13.0rc1 实验性自由线程版本(main,2024年8月13日,11:52:30)[GCC 12.2.0]
当前 GIL 已禁用
函数 'single_threaded_task' 执行的时间为 7.5143 秒。
函数 'multi_threaded_task' 执行的时间为 1.3645 秒。
函数 'multi_processing_task' 执行的时间为 1.6192 秒。
## PYTHON_GIL=1 (启用 GIL)使用 Python 3.13-rc
$ PYTHON_GIL=1 miteshsjat/python:3.13-rc ./gil_test2.py
Python 版本:3.13.0rc1 实验性自由线程版本(main,2024年8月13日,11:52:30)[GCC 12.2.0]
当前 GIL 已启用
函数 'single_threaded_task' 执行的时间为 7.5105 秒。
函数 'multi_threaded_task' 执行的时间为 7.0728 秒。
函数 'multi_processing_task' 执行的时间为 1.4612 秒。
## PYTHON_GIL=1 (启用 GIL)使用 Python 3.12;
## 此处设置 PYTHON_GIL=1/0 不会产生任何效果
$ PYTHON_GIL=1 python:3.12-slim-bookworm ./gil_test2.py
Python 版本:3.12.5(main,2024年8月13日,01:30:38)[GCC 12.2.0]
Python 3.12 及以下版本无法禁用 GIL
函数 'single_threaded_task' 执行的时间为 8.0055 秒。
函数 'multi_threaded_task' 执行的时间为 7.5204 秒。
函数 'multi_processing_task' 执行的时间为 1.5135 秒。
当我们禁用全局解释器锁(GIL)时,我们的多线程任务完成得飞快——只需 1.36秒!这比GIL处于活动状态时快了 六 倍。有趣的是,在Python 3.13中使用活动的GIL,结果与Python 3.12非常相似(Python 3.12不支持禁用GIL)。
zh: 结论
在这篇文章中,我们探讨了 Python 3.13 的新特性及其如何通过可选的 GIL 实现真正的并发。无论您是在构建高性能的 Web 应用程序还是计算密集型的数据处理管道,这项功能无疑将为您的多线程任务带来革命性的影响!
共同学习,写下你的评论
评论加载中...
作者其他优质文章