深入理解 Python GIL:从原理到实践

写 Python 写了几年,你或许曾遇到过这样的困惑:明明给数据处理加了多线程,CPU 占用率飙上去了,速度却几乎没变。这不是你的代码有 bug,而是 GIL 在起作用。 本文从原理出发,用实验数据说话,帮你彻底搞清楚 GIL 的边界——以及在机器学习、深度学习、量化金融场景下如何系统性地绕开它。 实验环境:Apple M3 Pro,Python 3.11.13,numpy 2.2.3,pandas 2.3.3,torch 2.11.0 实验代码:https://github.com/Coldeye2020/gil_experiments 一、GIL 是什么,为什么存在? 一句话定义:GIL(Global Interpreter Lock,全局解释器锁)是 CPython 中的一把互斥锁,保证同一时刻只有一个线程在执行 Python 字节码。 从引用计数说起 CPython 用引用计数(reference counting)管理内存。每个 Python 对象都维护一个 ob_refcnt 字段,记录当前有多少个引用指向它;当计数归零时,对象被立即释放。 这个机制简洁高效,但存在一个问题:如果多个线程同时对同一个对象的引用计数做 +1 或 -1,就会产生竞态条件(race condition)——两个线程同时读到旧值 n,各自写回 n+1,结果只加了一次而不是两次。这会导致内存泄漏,甚至在错误时机释放仍在使用的对象,引发崩溃。 理论上可以给每个对象的引用计数加一把细粒度锁,但这意味着几乎每次对象访问都要加锁解锁,开销极大。CPython 的设计者 Guido van Rossum 在 1990 年代初选择了一个更简单的方案:加一把大锁,锁住整个解释器。这把锁就是 GIL。 用代码验证引用计数 import sys a = [] print(sys.getrefcount(a)) # 输出 2:a 本身持有 1 个引用,getrefcount 调用时临时持有 1 个 b = a print(sys.getrefcount(a)) # 输出 3:b 也引用了同一个列表 del b print(sys.getrefcount(a)) # 输出 2:b 被删除,引用数减 1 sys.getrefcount() 让我们直接观察引用计数的变化。正是这个机制的存在,让 GIL 成为 CPython 的"必要之恶"。 ...

Date: April 19, 2026 | Estimated Reading Time: 27 min | Author: Coldeye