如果你曾经尝试通过多线程提升Python的性能,那么你肯定遇到过一个老对手——全局解释器锁(GIL)。这个讨人厌的小家伙会阻止Python线程真正实现并行运行。但是嘿!GIL已经是个老生常谈的话题了,如今,随着Python 3.13推出了实验性的NO-GIL(自由线程)版本,终于有机会动手试试看这到底意味着什么!
正式版本预计在假期推出,现在目前是 RC2 版本
GIL是个啥?为什么Python需要它?
简单来说,GIL是Python的“防守大将”,确保同一时刻只有一个线程在执行Python字节码。那这是为什么呢?Python依赖引用计数来进行内存管理。如果多个线程同时修改对象的引用计数,而没有锁机制的保护,数据可能会遭到破坏。
也就是说,GIL存在让Python的内存管理更简单,线程更安全,但代价就是:CPU密集型任务无法真正并行。不管你的CPU有多少核心,线程在遇到计算密集型任务时,总是只能一个个排队执行。
Python 3.13 与 NO-GIL 实验版登场
Python 3.13的NO-GIL实验(即自由线程)试图打破这种限制。通过移除GIL,Python线程可以真正实现并行,充分利用多核处理器的优势。这听起来是不是很棒?
那么,接下来我们看看我通过Docker在Python 3.13的GIL版本和NO-GIL版本上进行的一些实验,看看实际效果如何。
实验设置
我进行了两组测试:
- 计算密集型任务:一个简单的循环,进行大量的数字相加。
- I/O密集型任务:通过多线程从
https://example.com
获取网页内容。
测试脚本如下:
import sys
import time
import requests
import threading
# 例子:输出 Python 版本
print(f"Python version: {sys.version}")
# 模拟任务
def compute_task():
total = 0
for i in range(10**7):
total += i
# 记录开始时间
start_time = time.time()
# 创建并启动线程
threads = [threading.Thread(target=compute_task) for _ in range(100)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
# 记录结束时间
end_time = time.time()
# 输出执行时间
print(f"Total execution time for compute_task: {end_time - start_time:.4f} seconds")
def fetch_url():
response = requests.get('https://example.com')
return response.content
# 记录开始时间
start_time = time.time()
# 创建并启动线程
threads = [threading.Thread(target=fetch_url) for _ in range(200)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
# 记录结束时间
end_time = time.time()
# 输出执行时间
print(f"Total execution time for fetch_url: {end_time - start_time:.4f} seconds")
python镜像
使用docker 镜像,免除编译
sudo docker pull registry.gitlab.com/python-devs/ci-images:active
sudo docker run -it registry.gitlab.com/python-devs/ci-images:active /bin/bash
sudo docker run registry.gitlab.com/python-devs/ci-images:active python3.13t -c "import sys; print(sys.version)"
# 输出:3.13.0rc2 experimental free-threading build (main, Sep 25 2024, 17:05:31) [GCC 13.2.0]
# 把 3.13t 改成 3.13
# 输出: 3.13.0rc2 (main, Sep 25 2024, 17:09:53) [GCC 13.2.0]
可以看到有 free-threading
版本的,带 GIL 的版本
测试
- GIL 版本测试命令
sudo docker run --rm -v /opt/bdp/data01/ :/usr/src/ -w /usr/src/ registry.gitlab.com/python-devs/ci-images:active bash -c "python3.13 -m venv venv13 && source venv13/bin/activate && pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple && python3.13 test.py"
# 输出:
Python version: 3.13.0rc2 (main, Sep 25 2024, 17:09:53) [GCC 13.2.0]
Total execution time for compute_task: 45.5343 seconds
Total execution time for fetch_url: 6.4674 seconds
- NO-GIL 版本测试命令
sudo docker run --rm -v /opt/bdp/data01/ :/usr/src/ -w /usr/src/ registry.gitlab.com/python-devs/ci-images:active bash -c "python3.13t -m venv venv13t && source venv13t/bin/activate && pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple && python3.13t test.py"
# 输出:
Python version: 3.13.0rc2 experimental free-threading build (main, Sep 25 2024, 17:05:31) [GCC 13.2.0]
Total execution time for compute_task: 3.7221 seconds
Total execution time for fetch_url: 1.9412 seconds
版本 | 计算密集型任务 | IO密集型任务 |
---|---|---|
GIL | 45.5s | 6.5s |
NO-GIL | 3.7s | 1.9s |
提升幅度 | 12.30 倍 | 3.42 倍 |
可以看到 NO-GIL 版本的 python 在计算密集型的任务上,有非常大的性能提升
NO-GIL 的优势与挑战
从实验中可以看出,NO-GIL版本对计算密集型任务的提升尤为显著。通过自由线程,Python终于能在多核处理器上高效并行处理任务,这对处理大量数据、AI训练等需要高性能计算的场景极具吸引力。
然而,NO-GIL的实现并非没有挑战:
- 兼容性问题:许多第三方扩展库依赖于GIL的存在,移除GIL可能会导致这些库出现线程安全问题。
- 复杂性增加:没有了GIL,Python的内存管理和多线程设计会变得更加复杂,程序员需要更小心地处理线程间的竞争条件。
- 性能调优:虽然NO-GIL在计算密集型任务中表现出色,但在某些场景下,线程切换和同步的开销可能反而拖慢速度。