大家好,我是正在实战各种 AI 项目的程序员晚枫。
为什么多线程不能加速 CPU 密集型任务?Python 的 GIL(全局解释器锁)是罪魁祸首。这一讲,我们彻底搞懂它。
📖 开篇:多线程为什么不加速?
1 | import threading |
罪魁祸首:GIL(Global Interpreter Lock)
🔒 GIL 是什么?
1 | // Python/ceval_gil.h |
GIL 的作用
1 | 线程A ──→ 获取 GIL ──→ 执行字节码 ──→ 释放 GIL ──→ |
GIL 确保:同一时刻,CPython 解释器中只有一个线程在执行 Python 字节码。
为什么需要 GIL?
原因:引用计数!
1 | // 每次创建/销毁对象时,需要修改引用计数 |
GIL 让引用计数的操作「原子化」,不需要额外的锁,成本最低。
⚡ GIL 的释放时机
GIL 不是一直持有,而是周期性释放:
时机1:IO 操作
1 | // 文件/网络/数据库等 IO 操作时,Python 会释放 GIL |
1 | # IO 密集型任务可以从多线程获益 |
时机2:字节码计数(Python 3.2+)
1 | // 每执行 N 条字节码指令后,释放 GIL |
Python 版本演进
| 版本 | GIL 行为 |
|---|---|
| Python 2 | 每 100 条字节码检查一次 |
| Python 3.2 | 改为 15ms 时间片,减少切换开销 |
| Python 3.11+ | 优化了 GIL 的获取/释放速度 |
| Python 3.13 (实验) | 无 GIL 模式(PEP 703) |
🎯 应对策略
CPU 密集型 → 多进程
1 | from multiprocessing import Pool |
IO 密集型 → 线程或异步
1 | # 方式 1:多线程 |
1 | # 方式 2:asyncio(推荐!) |
混合场景 → ProcessPoolExecutor
1 | from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor |
🧪 性能对比实验
1 | import threading, multiprocessing, time |
💡 本节作业
- 用 timeit 对比:多线程 vs 多进程处理 CPU 密集型任务
- 用 asyncio 重写一个 IO 密集型任务
- 搜索:Python 3.13 的 No-GIL 模式是什么进展?
🎯 本讲总结
GIL 原理:全局互斥锁,同一时刻只有一个线程执行 Python 字节码。
GIL 原因:保护引用计数,避免线程安全问题(最低成本方案)。
释放时机:IO 等待时、字节码计数耗尽时(~15ms)。
应对策略:CPU 密集型用多进程(ProcessPoolExecutor),IO 密集型用 asyncio/线程。
📚 推荐教材
《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》
🔗 课程导航
← 上一讲:栈帧与调用约定 | 下一讲:线程与并发 →
💬 联系我
| 平台 | 账号/链接 |
|---|---|
| 微信 | 扫码加好友 |
| B 站 | Python 自动化办公社区 |
主营业务:AI 编程培训、企业内训、技术咨询