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

Python2.7与Python3.x线程操作结果差异原因咨询

Python 2.7与3.x中无锁多线程全局变量操作的差异原因

问题场景

某threading.Lock教程给出一个无锁多线程操作同一全局变量的示例,代码如下:

import threading

num = 0

def add():
    global num
    for i in range(1000000):
        num += 1

def desc():
    global num
    for i in range(1000000):
        num -= 1

def main():
    t1 = threading.Thread(target=add)
    t2 = threading.Thread(target=desc)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(num)

main()

多次运行后发现:Python 2.7中结果每次不同,符合无锁竞态的预期;但Python 3.10中结果始终为0。疑惑num +=1和num -=1是否为原子操作,以及为何跨版本表现不同,需要兼容Python2.7和3.x的脚本解决方案。

核心原因解析

  1. 加减操作并非原子操作
    无论Python版本,num +=1和num -=1都不是原子指令,实际会拆分为三个独立步骤:

    • 读取当前num的内存值
    • 计算新值(加1/减1)
    • 将新值写回num的内存地址
      线程在这三步之间发生调度切换时,就会导致竞态条件,最终结果偏离预期。
  2. Python版本间GIL调度机制的差异

    • Python 2.7:GIL的线程调度基于字节码计数阈值(默认100字节码),线程执行到阈值就会主动释放GIL。这种调度方式很容易让线程在加减操作的三步之间切换,导致大量竞态发生,结果随机。
    • Python 3.2+:GIL调度改为基于时间片(默认5ms),同时优化了字节码执行的连续性。在测试代码中,循环内的加减操作字节码序列较短且连续,线程可能在完整执行多次循环后才会触发调度切换,刚好抵消了竞态的影响,表现出结果始终为0。但这是偶然的优化结果,并非操作变成了原子性——如果增大循环次数,或者在循环中加入微小延时(如time.sleep(0.000001)),Python3.x依然会出现非0结果。

兼容多版本的正确写法

无论哪个Python版本,必须使用threading.Lock来保证加减操作的原子性,才能避免竞态问题。修正后的代码如下:

import threading

num = 0
lock = threading.Lock()

def add():
    global num
    for i in range(1000000):
        with lock:
            num += 1

def desc():
    global num
    for i in range(1000000):
        with lock:
            num -= 1

def main():
    t1 = threading.Thread(target=add)
    t2 = threading.Thread(target=desc)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(num)

main()

使用with lock可以自动获取和释放锁,确保每次加减操作的三步都在锁的保护下完成,无论哪个版本运行,结果都会稳定为0。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:59:59