Python3.10 下为什么没有多线程自增安全问题了?

Python3.10 下为什么没有多线程自增安全问题了?

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 本身没有对这里的线程安全做出保证,说不准到以后某个版本它又不安全了。需要线程安全,还是要加锁。

>>> 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

回到顶部