Python GIL 与线程
CPython GIL 限制同一时刻多线程执行 Python 字节码;线程仍利于阻塞 I/O,共享状态需锁。
#type / concept
#status / growing
#tech / dev
#resource / python
#tech / lang / python
[!info] 关联笔记
Python GIL 与线程
这个概念为什么出现
“开了 8 线程为什么 CPU 密集任务几乎不加速?”——在 CPython 中,GIL(全局解释器锁)保证同一进程内多线程交错执行 Python 字节码,而非真正并行解释。
但线程对阻塞 I/O 仍然有价值:等待时其他线程可运行。
[!abstract] 一句话理解 GIL 是 CPython 实现细节:限制多线程 Python 字节码并行;I/O 等待或释放 GIL 的 C 扩展仍可重叠,共享内存要用同步原语。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:并行下载元数据(I/O)用线程池
# thread_io_demo.py
# 业务意图:并发“下载”多个资源元数据(sleep 模拟 I/O)。
# 教学点:
# - ThreadPoolExecutor;
# - I/O 等待可重叠;
# - 不用 sleep 冒充锁同步。
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import perf_counter, sleep
def fetch_meta(item_id: str) -> str:
sleep(0.2) # 模拟网络阻塞,释放 GIL 等待
return f"meta:{item_id}"
def main() -> None:
ids = [f"id-{i}" for i in range(5)]
t0 = perf_counter()
with ThreadPoolExecutor(max_workers=5) as pool:
futs = [pool.submit(fetch_meta, i) for i in ids]
results = [f.result() for f in as_completed(futs)]
print("count:", len(results), "elapsed≈", round(perf_counter() - t0, 2))
if __name__ == "__main__":
main()
建议运行:
python thread_io_demo.py
期望输出(耗时约 0.2s 量级,而非 1.0s):
count: 5 elapsed≈ 0.2
结合场景再看三个关注点
- 阻塞等待重叠带来加速。
- 若换成纯 Python 重计算,加速会很有限。
- 线程间改共享 list/dict 需要锁或队列。
核心概念与准确模型
threading.Thread/concurrent.futures.ThreadPoolExecutor- GIL:保护对象内存管理等实现简化
- I/O、部分 C 扩展会释放 GIL
- 这是实现,不是语言规范永久承诺
边界情况与反直觉行为
- CPU 密集 + 多线程可能更慢(切换开销)。
- 死锁:锁顺序不一致。
- 守护线程与解释器退出行为需小心。
常见误区
[!warning] 常见误区:GIL 表示 Python 不能并发 错误理解:完全不能重叠任务。
正确模型:不能并行解释字节码 ≠ 不能并发等待 I/O。
工程实践
- 池大小与下游容量匹配
- 共享状态优先队列消息
- CPU 密集换进程/原生
本节总结
线程 + GIL 的正确叙事是 I/O 并发工具,不是多核算力工具。
自测题
- 为什么示例 elapsed 约 0.2 不是 1.0?
- GIL 是语言规范吗?
参考答案
- 五线程并行等待 I/O。
- 否,主要是 CPython 实现细节。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| threading | 文档 | 线程 API |
| GIL glossary | 术语 | GIL |