Python3.10 下为什么没有多线程自增安全问题了?
import threadinga = 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.py1 2632601
2 2767037
2 3000000
全局 3000000
使用 python3.9 运行,结果不是 3000000 ❓
─➤ python3.9 001.py1 661812
2 1137151
2 1650809
全局 1650809
使用 python3.9 运行,结果不是 3000000 ❓
─➤ python3.8 001.py2 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 本身没有对这里的线程安全做出保证,说不准到以后某个版本它又不安全了。需要线程安全,还是要加锁。
>>> def a(x): x+=1...
>>> import dis
>>> dis.dis(a)
1 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (x)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
>>> def a(x): x=x+1
...
>>> import dis
>>> dis.dis(a)
1 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (x)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
>>>
回答:
根据一名Python核心开发成员的描述:
Mark Shannon 重构快速操作码调度的更改的意外后果: https://github.com/python/cpy... -- INPLACE_ADD 操作码不再使用检查中断等的“慢”调度路径。
已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。
以上是 Python3.10 下为什么没有多线程自增安全问题了? 的全部内容, 来源链接: utcz.com/p/938464.html