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的脚本解决方案。
核心原因解析
加减操作并非原子操作
无论Python版本,num +=1和num -=1都不是原子指令,实际会拆分为三个独立步骤:- 读取当前
num的内存值 - 计算新值(加1/减1)
- 将新值写回
num的内存地址
线程在这三步之间发生调度切换时,就会导致竞态条件,最终结果偏离预期。
- 读取当前
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
相关产品推荐
相关产品推荐

