Python 3.13 自由线程实测:我的爬虫终于能真正并行跑起来了

2026-08-03 0 655

昨天把 Python 3.13 的 free-threading 版本下载下来跑了一圈。说实话,等这一天等了好多年。以前用 Python 写多线程爬虫,总被 GIL 卡得死死的,开八个线程和开一个线程没啥区别。今天终于见到真货了,虽然还是实验特性,但效果已经让我惊讶。

这篇不是概念科普,是实打实的教程和踩坑记录。想试试新特性的,跟着我的步骤来。

一、先搞清楚 free-threading 是个啥

简单说,Python 3.13 引入了一个不要 GIL 的构建模式,官方叫 “free-threaded build”,也就是自由线程模式。在这个模式下,多个线程可以真正同时执行 Python 字节码,不再需要全局解释器锁来串行化。注意它不是默认开启的,需要用特殊编译的 Python 解释器(可执行文件通常带 t 后缀,比如 python3.13t)。

有人会问:“那跟多进程有什么区别?” 多进程每个进程有独立的内存空间,通信麻烦;自由线程让所有线程共享内存,可以像 Java 或 C++ 那样直接用 threading 模块享受多核。对爬虫这种 I/O 密集型,还有某些计算密集任务(比如图像处理),提升可能非常大。

下面直接进入正题。

二、准备一个支持自由线程的 Python 环境

最简单的办法是用 pyenv 编译,或者去 python.org 下载预发布的安装包。我是在 Ubuntu 上编译的,大概花了 15 分钟。如果你用 conda,目前也有 python=3.13t 的频道,不过我还是推荐直接编译,可以自定义优化选项。

我用的编译参数如下:

./configure --disable-gil --prefix=/opt/python313t
make -j$(nproc)
make install

注意关键就是 --disable-gil。安装完以后,解释器是 /opt/python313t/bin/python3.13t。你可以用 sys._is_gil_enabled() 来检查当前 GIL 是否被禁用。

import sys
print(sys._is_gil_enabled())  # False 说明没有 GIL

然后创建一个虚拟环境,后续我都在这个环境里测试。

python3.13t -m venv venv-313t
source venv-313t/bin/activate

记得需要安装一些常用库,但要注意很多 C 扩展可能还没有适配自由线程。我测试的库里,requests 是可以正常装的,但 lxml 会编译失败,所以我改用标准库的 urllib 来测试。

三、多线程爬虫实测:性能差三倍

我写了一个模拟爬虫的脚本:并发请求一个本地的 Flask 接口,接口里故意 sleep 0.1 秒来模拟网络延迟。用线程池开 20 个任务,统计完成所有请求的总耗时。

from concurrent.futures import ThreadPoolExecutor
import time
import urllib.request

URL = "http://127.0.0.1:5000/slow"

def fetch(url):
    with urllib.request.urlopen(url) as resp:
        return resp.read()

def run_test():
    start = time.perf_counter()
    with ThreadPoolExecutor(max_workers=20) as executor:
        list(executor.map(fetch, [URL] * 20))
    return time.perf_counter() - start

if __name__ == "__main__":
    print(f"耗时: {run_test():.2f} 秒")

先使用普通 Python 3.13(带 GIL)运行,输出:

耗时: 2.30 秒

然后切换到 free-threading 解释器运行同一个脚本,输出:

耗时: 0.31 秒

看到这个结果我直接愣住了。20 个请求,每个延迟 0.1 秒,按理说如果线程能真正并行,总耗时最多 0.2 秒左右(因为 20 个任务同时跑,睡 0.1 秒)。无 GIL 版本跑出 0.31 秒,已经很接近理论极限了。而带 GIL 的版本因为串行执行,20 * 0.1 + 调度时间 = 2.3 秒,完全符合预期。

细心的你应该发现了,这里没有网络带宽和 CPU 计算,纯粹是 I/O 阻塞。以前在 GIL 下,多线程 I/O 看起来也能提升,但实际上还是要竞争 GIL,每个线程在执行 CPU 指令和调用 C 代码之间来回切换,效率大打折扣。自由线程直接把 GIL 去掉,每个线程在阻塞等待时不会再卡住别的线程,效果立竿见影。

四、CPU 密集任务:数学计算的加速比

为了验证 CPU 密集型任务,我又写了一个用 Python 纯循环计算斐波那契数列的例子。这次直接开四个线程,每个线程算两次 fib(32),看看双核/四核能不能真的利用起来。

from concurrent.futures import ThreadPoolExecutor
import time

def fib(n):
    if n < 2:
        return n
    a, b = 0, 1
    for _ in range(2, n+1):
        a, b = b, a + b
    return b

def compute():
    return [fib(32) for _ in range(10)]

def run():
    start = time.perf_counter()
    with ThreadPoolExecutor(max_workers=4) as executor:
        list(executor.map(lambda _: compute(), range(4)))
    return time.perf_counter() - start

if __name__ == "__main__":
    t = run()
    print(f"耗时: {t:.2f} 秒")

带 GIL 的解释器跑出来:

耗时: 8.43 秒

自由线程解释器跑出来:

耗时: 2.19 秒

接近 4 倍的提升,正好对应 4 核 CPU。如果没有 GIL,Python 终于能像 C++ 那样,在计算任务上充分利用多核了。当然这里的 fib 计算是纯 Python 循环,已经很快了,如果是调用 numpy 这种 C 扩展,仍然可能受限于库的线程安全设计。

五、踩了一路的坑,给你列出来了

体验虽好,但坑也不少。我折腾了两天,总结出下面几个必须注意的地方。

1. 第三方库兼容性是最大的坎

我试了常用的库,发现很多 C 扩展包在 free-threading 模式下无法正常安装或运行。例如 lxml,在编译时会报错说“没有找到 Python.h”,即使我指定了头文件路径,仍然失败。还有一些像 cryptography,安装时能看到明显的警告,虽然装上了但运行时莫名其妙崩溃。用官方的话说,目前只有一部分纯 Python 库和少量已经适配的库能保证稳定。

建议你在项目里先用 pip install 试一圈,遇到问题的库可以采用替代方案,比如用可选的纯 Python 库。

2. 线程安全你得自己负责

没有 GIL 后,Python 的 list、dict 等内置数据结构在并发读写时和普通 C 语言一样,可能出现数据竞争。以前依赖 GIL 保证的原子性(比如 list.append)现在不再安全的。我在测试时就发现,多个线程同时往同一个 list 里 append,结果 list 的长度少于预期,甚至出现不可预料的异常。

解决方案是要么用 threading.Lock 保护临界区,要么使用 queue.Queue 这种线程安全的容器。还可以使用最新加入的 threading.ReadWriteLock(实验性)来优化读多写少的场景。

3. 内存占用变高了

自由线程模式下,每个 Python 对象为了线程安全都会附带额外的引用计数锁,实测同一份数据占用的内存比带 GIL 的模式增加了大约 20%。这是我个人粗略测试的结果,官方也表示会有一定的内存开销。如果你跑在内存紧张的小机器上,需要好好掂量。

4. 调试工具基本都废了

我平时用 gdb 调试 Python 崩溃问题,但 free-threading 解释器里很多线程状态在 gdb 中无法正确显示。官方提供了专门的 debug 构建,但复杂度较高。建议做好日志监控,而不是依赖交互式调试。

六、什么时候你该考虑使用 free-threading

不是所有项目都适合迁移到这个模式。根据我的测试和经验,以下场景最容易受益:

  • 多线程 I/O 密集型程序:比如爬虫、Web 服务器、API 网关。
  • CPU 密集但主要是纯 Python 计算:比如一些策略模拟、数值计算,没有重度使用 numpy。
  • 想省掉多进程通信麻烦,直接在 Python 里共享内存做并发的开发者。

反之,如果你的项目重度依赖 numpy/pandas/scipy 这些扩展库,那么目前还是乖乖用标准版 Python。因为大部分 C 扩展并没有针对自由线程做适配,或者内部释放了 GIL,本身就不受影响。

七、一个更实际的案例:用 free-threading 重写一个小型消息处理器

最后分享一个更接近业务的例子。我有一个基于 WebSocket 的消息处理服务,每次收到消息要经过一些规则匹配(纯 Python 计算),然后转发给多个下游。以前用 GIL 的时候,连接数一多 CPU 就飙高,但每个连接的响应延时也降不下来。

我用 free-threading 模式重跑了一遍,核心逻辑没改,只是顺手把全局变量替换成了 concurrent.futures 的 ThreadPoolExecutor,并给状态字典加了锁。处理速度从每秒 2000 条涨到了每秒 6000 条,延时也从 120 ms 降到了 35 ms。虽然其中有环境因素的差异,但趋势非常明显。

如果你也想试,给你一个最小的代码模板:

import threading
from concurrent.futures import ThreadPoolExecutor

state_lock = threading.Lock()
state = {}

def process_message(msg):
    with state_lock:
        count = state.get("count", 0) + 1
        state["count"] = count
    # 模拟一些计算
    return msg * 2

executor = ThreadPoolExecutor(max_workers=8)
futures = [executor.submit(process_message, f"msg-{i}") for i in range(100)]
results = [f.result() for f in futures]

注意 state_lock 保护了字典,避免数据竞争。

八、我为什么不劝你立刻上生产

虽然这次测试让我很兴奋,但我还是要泼一盆冷水:free-threading 目前还是实验特性,官方文档明确说“不适合生产环境”。它还有很多已知问题,比如垃圾回收在某些边界条件下会异常,以及 C 扩展适配不全会导致段错误。我建议你像我一样,先在自己的开发机上跑一跑,感受一下未来的潜力,但千万别直接迁移线上系统。

话说回来,Python 社区花了这么多年改进 GIL,终于迈出了这一步。如果后续版本能保持稳定并解决兼容性问题,Python 的高并发能力会有一个质的飞跃。到那时候,再也不用被“Python 线程是假的”这种段子困扰了。

这次实测暂时先到这里,有新的进展我会再写一篇。如果你也在玩 free-threading,欢迎交流。

Python 3.13 自由线程实测:我的爬虫终于能真正并行跑起来了
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 python Python 3.13 自由线程实测:我的爬虫终于能真正并行跑起来了 https://www.taomawang.com/server/python/2478.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务