You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CPython存在GIL时,为何仍需使用threading.Lock()?

为什么在CPython中即使有GIL也需要threading.Lock()?

你这个疑问其实特别典型——刚接触Python线程的人很容易被GIL的“同一时间一个线程执行”给误导,觉得根本不需要额外加锁,但实际情况远不是这样。

先掰扯清楚GIL的本质:它确实保证了同一时刻只有一个线程在跑Python字节码,但它不是一直攥着不放的。CPython会在特定时机主动释放GIL,比如执行了一定数量的字节码后,或者遇到文件I/O、网络请求这类阻塞操作时。这就意味着,线程的执行随时可能被打断,切换到另一个线程继续跑。

举个最直观的例子,比如你用两个线程给一个全局变量累加:

import threading

count = 0

def add_one():
    global count
    for _ in range(100000):
        count += 1

t1 = threading.Thread(target=add_one)
t2 = threading.Thread(target=add_one)

t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终结果: {count}")

你猜最终结果是200000?大概率不是,实际跑出来的数会比这个小很多。原因很简单:count += 1看起来是一行代码,但在Python字节码层面是三个步骤:

  1. 读取当前count的值
  2. 把这个值加1
  3. 将新值写回count

假设线程1刚做完前两步,GIL突然被释放,线程2开始执行。线程2读取的还是原来的count值,加1后写回去。等线程1重新拿到GIL,它会把自己计算的新值也写回去——这就直接覆盖了线程2的修改,相当于白加了一次。

这时候threading.Lock()就派上用场了,我们用它把这个复合操作变成原子的:

import threading

count = 0
lock = threading.Lock()

def add_one():
    global count
    for _ in range(100000):
        with lock:
            count += 1

t1 = threading.Thread(target=add_one)
t2 = threading.Thread(target=add_one)

t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终结果: {count}")

with lock会自动帮我们获取锁,执行完代码块后再释放。这样一来,count += 1的三个步骤就会作为一个整体执行,中间不会被其他线程打断,最终结果肯定是200000。

除了这种简单的累加,还有很多场景需要锁:比如你要操作共享的列表(先判断长度再弹出元素)、字典(先检查键是否存在再赋值),这些组合操作都不是原子的,哪怕单个方法是线程安全的,组合起来就有风险。

总结一下:GIL只保证了单个字节码指令的原子性,但对于多个字节码组成的复合操作,线程安全问题依然存在,这时候就必须用threading.Lock()来保护临界区。

内容的提问来源于stack exchange,提问作者wings

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:26:00