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

为何Python 3.10中多线程可正常更新全局变量?

为什么Python 3.10中多线程更新全局变量不再出现竞态条件?

我正在教授Python课程,梳理多线程相关内容时遇到一个困惑:之前用下面的代码向学生演示两个线程同时修改全局变量会引发竞态条件,但在Python 3.10版本中,无论在Windows还是Linux上运行多少次,计数器最终都能得到2_000_000的"正确"值,完全没有混乱情况。想请教这背后的原因是什么?

#!/usr/bin/env python3
"""Example of a simple race condition"""

import threading

MAX = 1_000_000

# global variable
counter: int = 0


def count() -> None:
    """Target function"""
    global counter
    for _ in range(MAX):
        counter += 1


def main() -> None:
    """Main function"""
    thread1 = threading.Thread(target=count)
    thread2 = threading.Thread(target=count)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f"{counter = :_}")


if __name__ == '__main__':
    main()

核心原因:GIL调度时机与字节码执行阈值

你看到的"无竞态条件"并不是因为counter +=1变成了原子操作,而是CPython的全局解释器锁(GIL)的调度逻辑在Python 3.10中调整后,你的测试代码刚好触发了串行执行的场景:

  • CPython的GIL会限制同一时间只有一个线程执行Python字节码,默认情况下,解释器每执行100个字节码指令后会主动释放GIL,让其他线程有机会执行。
  • 你的count()函数里,每次counter +=1对应的字节码序列非常短(仅几个指令),而100万次迭代的总字节码数量虽然多,但单个线程在执行完所有迭代前,可能因为字节码计数还没达到阈值,不会主动释放GIL。这样线程1会先完整执行完100万次自增,再释放GIL给线程2,最终两个线程串行执行,自然得到正确结果。

如何重现竞态条件?

如果想让学生看到预期的混乱结果,可以修改代码,让线程在迭代过程中主动释放GIL,比如加入微小的延迟:

#!/usr/bin/env python3
"""Example that reliably shows race condition"""

import threading
import time

MAX = 1_000_000

counter: int = 0


def count() -> None:
    global counter
    for _ in range(MAX):
        counter += 1
        # 加入微小延迟,强制触发GIL释放
        time.sleep(0.000001)


def main() -> None:
    thread1 = threading.Thread(target=count)
    thread2 = threading.Thread(target=count)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f"{counter = :_}")


if __name__ == '__main__':
    main()

或者把自增操作拆分成多个步骤,让单次迭代的字节码数量增加,迫使GIL在迭代中间释放:

def count() -> None:
    global counter
    for _ in range(MAX):
        # 拆分成多个步骤,增加字节码数量
        temp = counter
        temp += 1
        counter = temp

这样修改后,线程会频繁释放GIL,两个线程的操作交替执行,就能明显看到计数器最终值小于2_000_000的竞态条件现象。

补充说明

  • 即使在Python 3.10中,counter +=1依然不是原子操作,它本质上是counter = counter + 1,包含读取当前值、计算新值、写入新值三个独立步骤,只要线程在这三个步骤之间被切换,就会出现竞态条件。
  • 你的测试代码只是刚好因为GIL的调度逻辑,没有触发线程切换,才得到了"正确"结果,这是一种巧合场景,不能说明多线程修改全局变量是安全的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 10:10:34