请问 Python 3.11 版本是对线程安全做了什么更改吗?

2023-08-17 07:39:06 +08:00
 ohayoo
import threading

num = 0


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


def sub():
    global num
    for i in range(10_000_000):
        num -= 1


if __name__ == "__main__":

    add_t = threading.Thread(target=add)
    sub_t = threading.Thread(target=sub)

    add_t.start()
    sub_t.start()

    add_t.join()
    sub_t.join()

    print("num result : %s" % num)

昨天偶然发现这份代码在 3.11.3 中它居然输出 0 ,一度以为自己写错了,抱着不信邪的态度,又搞了个 Python 3.9.7 的环境试了下,果然还是符合自己预期,输出不为 0

想问下 3.11 版本中是做了什么修改吗?

4352 次点击
所在节点    Python
46 条回复
joApioVVx4M4X6Rf
2023-08-17 07:58:26 +08:00
num -= 1 这个字节码在 3.9 和 3.11 不一样,可以用 dis 模块看一下
tulongtou
2023-08-17 08:30:43 +08:00
3.9/3.10/3.11 都测试了下,发现 3.10 输出就是 0 了。
这段代码正常不就应该是 0 么,难道 3.9 的是 bug ?
jstony
2023-08-17 08:35:25 +08:00
试了一下,3.8/3.9 输出不是 0 ,3.10/3.11 是 0
yuyang
2023-08-17 08:45:41 +08:00
因为 GIL 的存在,一堆错误的并发程序也能跑,好多人就得过且过,你说你纠结于这样一个错误的并发程序的结果有啥意义?
jstony
2023-08-17 08:46:51 +08:00
问了一下 gpt ,给了个关键词,gil ,可以看看这个解释: https://zhuanlan.zhihu.com/p/75780308
weidaizi
2023-08-17 09:12:18 +08:00
这个结果为 0, 1 都是有可能的,还有可能为 -1, 2, -2, 3, ...... 这和版本无关呀 : )
Vegetable
2023-08-17 09:15:14 +08:00
你没法保证设计之外的结果是稳定的,这代码有 bug ,所以结果不一样很长长
yph007595
2023-08-17 09:30:47 +08:00
你觉得应该是什么结果?我一看就觉得应该是 0 啊
fgwmlhdkkkw
2023-08-17 09:32:26 +08:00
多少都是合理的吧
deplivesb
2023-08-17 09:43:13 +08:00



3.9 里面+= 和-= 不是原子操作,所以线程不安全
3.11 里面这俩是原子操作,所属已线程安全
jjx
2023-08-17 09:44:35 +08:00
这个 gil 只是保证同一时间一个线程

他不保证谋个线程 分配的时间长点,一个分配的时间断点

这个例子,出什么结果都有可能啊
ShadowPower
2023-08-17 09:46:40 +08:00
这跟 GIL 没什么关系。你可以把 GIL 锁想象成单核电脑上跑多线程的情况。
Python 的 GIL 锁没有哪个版本会对整个函数加锁。不然的话,对于多线程 GUI 程序,做任何耗时操作都会让整个界面未响应。

这个程序在任何版本的 Python 中跑出的结果都是无法预测的。
ohayoo
2023-08-17 09:47:33 +08:00
@v2exblog
@deplivesb
感谢两位,一针见血
deplivesb
2023-08-17 09:51:28 +08:00
楼上一堆说“在任何版本的 Python 下结果都是不可预测的”我只能说大人时代变了。3.10 之后 += 和 -= 的操作线程安全了。该更新一下 Python 版本享受一下新的特性了。
araraloren
2023-08-17 09:55:30 +08:00
抛开 GIL ,这个程序就是典型的并发问题程序,因为两个线程之间没有同步
GIL 有对线程安全保证吗?我想没有
weyou
2023-08-17 10:08:32 +08:00
@deplivesb 见 11 楼
deplivesb
2023-08-17 10:14:55 +08:00
@weyou 大哥,原子操作啥意思懂不? gil 是会对每个线程分片执行,但是最小粒度就是一个原子操作,为啥 3.9 之前结果不确定,因为+= 和-= 的操作是两个原子操作,gil 可能在中间中断,导致结果不同步。3.10 之后+= 和-= 是原子操作,gil 单步就执行完 += 和-= 操作了。
lovelylain
2023-08-17 10:32:50 +08:00
@deplivesb 不是 INPLACE_ADD 与 BINARY_OP 的原因,python 有个难以废除的 GIL ,所以单条字节码本身是原子的,只是一个操作往往会编译成一组字节码,例如你图中的 a+=1 就是两条 LOAD 一条计算一条 STORE ,如果没保证整体的原子性,就还是会有线程安全问题。INPLACE_系列替换为 BINARY_OP 好处是减少了字节码种数,但对这一组字节码并没有减少条数,所以区别不在这里。而且前面也有人说了,3.10 就线程安全了,3.10 还是 INPLACE_ADD 。
fgwmlhdkkkw
2023-08-17 10:38:04 +08:00
@deplivesb #14 还是两条指令啊
lovelylain
2023-08-17 10:55:28 +08:00
怀疑这里跟 opcode cache 有关,具体怎么影响的还不清楚

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/965954

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX