import threading
a = 0
def func(index):
global a
# with threading.Lock():
# for i in range(10000):
# a+=1
for i in range(1000000):
a = a+1
print(index, a)
t_1 = threading.Thread(target=func, args=[1])
t_1.start()
t_2 = threading.Thread(target=func, args=[2])
t_2.start()
t_3 = threading.Thread(target=func, args=[2])
t_3.start()
t_1.join()
t_2.join()
t_3.join()
print('全局', a)
一个简单的三线程累加的代码(不加锁)
使用 python3.10 运行,结果都是 3000000 ✅
─➤ python3.10 001.py
1 2632601
2 2767037
2 3000000
全局 3000000
使用 python3.9 运行,结果不是 3000000 ❓
─➤ python3.9 001.py
1 661812
2 1137151
2 1650809
全局 1650809
使用 python3.9 运行,结果不是 3000000 ❓
─➤ python3.8 001.py
2 1036512
2 1251107
1 1545036
全局 1545036
为什么 python3.10 下为什么没有多线程自增安全问题了?
这个变化没有在发行日志里面看到呀?
后背是做了什么导致的?
自动对自增操作加锁了?但是这样对于不需要多线程的场景不就变成累赘了吗?
扩写一下 @张京 的答案。
用 python 自己的 dis 看一下 opcode ,就知道无论是 x+=1 还是 x=x+1 ,做加法 (INPLACE_ADD, BINARY_ADD) 跟保存结果 (STORE_FAST) 都是两条 opcode 。
python 代码执行过程中,只有在主动检测中断的地方才可能发生线程切换。原来在很多 opcode 之后都去检测中断,修改后只有少数的指令才会去检测中断了。比如,原来在 INPLACE_ADD 或 BINARY_ADD 之后都可能发生线程切换,但是修改后,这两条 opcode 之后不会发生线程切换了。
由于在 xxx_ADD 与 STORE_FAST 之间不会有线程切换了,所以看起来 x = x + 1 变得线程安全了。(LOAD_FAST, LOAD_COST 这两个 opcode ,原来就没有检测中断)
这个修改本身好像是一个 bug fix ,但是不是针对这个线程安全问题的。对这个线程安全问题的影响只是一个副作用。 python 本身没有对这里的线程安全做出保证,说不准到以后某个版本它又不安全了。需要线程安全,还是要加锁。