Python 长期以来以其易用性和多功能性而闻名,但在 Python 社区中引发大量讨论的一个话题是全局解释器锁 (GIL)。 GIL 既是 Python 并发模型的保障,也是瓶颈,尤其是对于 CPU 密集型任务,否则这些任务可以利用多个 CPU 核心。然而,随着 Python 3.13 的发布,Python 开发人员有了一个突破性的新选项:禁用 GIL 的能力。本博客将探讨 GIL 是什么、为什么它成为多线程性能的障碍,以及如何在 Python 3.13 中检测和禁用 GIL 以释放真正的多线程性能。
全局解释器锁 (GIL) 是一个互斥锁,用于保护对 Python 对象的访问,防止多个本机线程同时执行 Python 字节码。这保证了Python程序的线程安全,但代价是并发执行。 GIL 使 Python 线程对于 I/O 密集型任务更加高效,但限制了它们对于 CPU 密集型任务的性能。
Python 的 GIL 只允许一个线程同时执行,即使在多线程程序中也是如此。虽然这对于程序等待输入/输出操作的 I/O 密集型任务来说很好,但它严重限制了 CPU 密集型任务(如数字运算、数据分析或图像处理)的性能。
使用 Python 3.13,开发人员可以选择在 Python 构建过程中禁用 GIL。但是,在预构建的 Python 发行版中无法禁用 GIL。相反,您必须使用 --disable-gil 选项从源代码编译 Python 3.13。
这个新选项为 CPU 密集型多线程任务中的真正并行性打开了大门,允许线程跨多个内核并行执行。
要使用 -X gil=0 标志禁用 GIL,您需要在启用 --disable-gil 标志的情况下从源代码编译 Python。具体方法如下
wget https://www.python.org/ftp/python/3.13.0/Python-3.13.0.tgz
tar -xf Python-3.13.0.tgz cd Python-3.13.0
./configure --disable-gil
make sudo make altinstall
./configure --disable-gil --prefix=$HOME/python3.13
make altinstall
在Python 3.13中,您可以使用sys._is_gil_enabled()函数检查GIL是否启用或禁用。
import sys def check_gil_status(): if sys.version_info >= (3, 13): status = sys._is_gil_enabled() if status: print("GIL is currently enabled.") else: print("GIL is currently disabled.") else: print("Python version does not support GIL status detection.") check_gil_status()
开发以下 Python 代码是为了评估在 Python 3.13 中禁用 GIL 时的性能增益。该脚本同时执行八个线程,每个线程的任务是计算大数的素因数。通过利用真正的并行性,代码突出了无需 GIL 即可实现的增强性能。
#!/usr/bin/env python3 import sys import sysconfig import time from threading import Thread from multiprocessing import Process # Decorator to measure execution time of functions def calculate_execution_time(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__} took {execution_time:.4f} seconds.") return result return wrapper # Compute-intensive task: Iterative Fibonacci calculation def compute_fibonacci(n): """Compute Fibonacci number for a given n iteratively.""" a, b = 0, 1 for _ in range(n): a, b = b, a + b return a # Single-threaded task execution @calculate_execution_time def run_single_threaded(nums): for num in nums: compute_fibonacci(num) # Multi-threaded task execution @calculate_execution_time def run_multi_threaded(nums): threads = [Thread(target=compute_fibonacci, args=(num,)) for num in nums] for thread in threads: thread.start() for thread in threads: thread.join() # Multi-processing task execution @calculate_execution_time def run_multi_processing(nums): processes = [Process(target=compute_fibonacci, args=(num,)) for num in nums] for process in processes: process.start() for process in processes: process.join() # Main execution function def main(): # Check Python version and GIL status for Python 3.13+ print(f"Python Version: {sys.version}") py_version = float(".".join(sys.version.split()[0].split(".")[: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 cannot be disabled for Python <= 3.12") elif status == 0: print("GIL is currently disabled") elif status == 1: print("GIL is currently active") # Run tasks on the same input size for comparison nums = [300000] * 8 print("\nRunning Single-Threaded Task:") run_single_threaded(nums) print("\nRunning Multi-Threaded Task:") run_multi_threaded(nums) print("\nRunning Multi-Processing Task:") run_multi_processing(nums) if __name__ == "__main__": main()
## Python 3.13 with GIL Disabled Python Version: 3.13.0 experimental free-threading build (main, Oct 14 2024, 17:09:28) [Clang 14.0.0 (clang-1400.0.29.202)] GIL is currently disabled Running Single-Threaded Task: run_single_threaded took 8.6587 seconds. Running Multi-Threaded Task: run_multi_threaded took 1.3885 seconds. Running Multi-Processing Task: run_multi_processing took 1.5953 seconds. ## Python 3.13 with GIL Enabled Python Version: 3.13.0 experimental free-threading build (main, Oct 14 2024, 17:09:28) [Clang 14.0.0 (clang-1400.0.29.202)] GIL is currently active Running Single-Threaded Task: run_single_threaded took 8.7108 seconds. Running Multi-Threaded Task: run_multi_threaded took 8.6645 seconds. Running Multi-Processing Task: run_multi_processing took 1.4530 seconds. ## Python 3.12 Python Version: 3.12.6 (main, Sep 7 2024, 19:30:10) [Clang 14.0.0 (clang-1400.0.29.202)] GIL cannot be disabled for Python <= 3.12 Running Single-Threaded Task: run_single_threaded took 8.7004 seconds. Running Multi-Threaded Task: run_multi_threaded took 8.6297 seconds. Running Multi-Processing Task: run_multi_processing took 1.4876 seconds.
多线程性能:禁用 GIL 的真正好处在多线程场景中是显而易见的:
禁用 GIL (3.13) 时,执行时间为 1.5703 秒。
启用 GIL 后(3.13),执行时间为 8.5901 秒。
结果:禁用 GIL 使多线程任务的性能提高了约 81.7%。
该图表清楚地表明,在 Python 3.13 中禁用 GIL 可以显着提升多线程 CPU 密集型任务的性能,从而使 Python 能够高效地并行利用多个 CPU 核心。虽然单线程和多处理性能基本上不受影响,但多线程性能显示出显着改进,使 Python 3.13 成为依赖多线程的 CPU 密集型应用程序的游戏规则改变者。
但是,3.13之前的Python版本不支持禁用GIL,这也解释了为什么它们的多线程性能仍然与启用GIL的Python 3.13相似。早期版本中的这一限制继续限制 Python 充分利用多线程处理 CPU 密集型任务的能力。
在 Python 3.13 中禁用全局解释器锁 (GIL) 可以显着提高多线程 CPU 密集型任务的性能。但是,在此之前需要考虑几个重要因素:
线程安全:如果没有 GIL,您必须使用锁或其他同步机制手动处理线程安全,以防止代码中的竞争条件。
潜在的性能下降:细粒度锁定可能会引入争用,这可能会降低以前受益于 GIL 的单线程或 I/O 密集型任务的性能。
与第三方库的兼容性:许多 C 扩展和库假设存在 GIL 以保证线程安全。禁用 GIL 可能需要更新这些库,以确保它们在多线程环境中正常工作。
复杂的内存管理:禁用 GIL 会增加内存管理的复杂性,需要线程安全的内存处理,这会增加错误和错误的风险。
I/O 密集型任务: 禁用 GIL 为 I/O 密集型任务带来的好处有限,在这些任务中,像 asyncio 这样的非阻塞 I/O 机制可能更有效。
调试困难:如果没有 GIL,由于竞争条件和死锁的可能性增加,调试多线程应用程序可能会变得更具挑战性。
更高的内存使用量: 在没有 GIL 的情况下使用锁和管理线程状态会增加内存消耗,特别是在多线程应用程序中。
嵌入式系统:禁用 GIL 可能会使 Python 与嵌入式系统中的多线程环境的集成变得复杂,需要付出更多努力才能有效集成。
锁争用:在某些情况下,禁用 GIL 可能会导致线程之间出现锁争用,这可能会降低预期的性能改进。
您可以在我的 GitHub 上找到此博客中示例的完整源代码:
Python GIL 性能分析
这是一个个人博客。本文表达的观点和意见仅代表作者的观点和意见,并不代表与作者有关联的任何组织或个人的专业或个人观点。
以上是Python 无需 GIL 即可实现高性能多线程的门户的详细内容。更多信息请关注PHP中文网其他相关文章!