关于 CPython 线程安全的问题

2019-05-31 03:37:24 +08:00
 tcdh

之前对 Python 理解不深,最近准备深入学习一下,在多线程和线程安全的时候碰到了一个问题。

https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe 里写到 Python 自带的数据结构的某些操作是不安全的。比如 D[x] = D[x] + 1

于是写了个很简单的测试:

import threading
import sys

sys.setswitchinterval(1)

d = {1:0}

def func():
        d[1] += 1
    
threads = []
for _ in range(1000000):
    t = threading.Thread(target=func)
    threads.append(t)
    t.start()

for thread in threads:
    thread.join()
    
print(d)

但是跑了很多遍结果都没有问题(打印{1: 100000})。用 dis 看了一下,确实 func()也是用了多行 bytecode,按理说应该有 race condition 才对。

>>> dis.dis(func)
 11           0 LOAD_GLOBAL              0 (d)
              2 LOAD_CONST               1 (1)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_CONST               1 (1)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

不太明白问题出在哪,是 100 万不够大吗?

2572 次点击
所在节点    Python
9 条回复
aijam
2019-05-31 03:39:52 +08:00
GIL 了解下
tcdh
2019-05-31 03:48:29 +08:00
@aijam 解释器在运行 bytecode 的时候 GIL 是有可能会丢的,我也加了``sys.setswitchinterval(1)``让这个频率高一些。按理说这种情况下往共享资源里写东西,又不是原子操作,是会有 race condition 的。
binux
2019-05-31 03:54:00 +08:00
或许 func 执行比线程创建快多了
tcdh
2019-05-31 03:58:02 +08:00
@binux 我也猜过这个可能,在 func 里面加过 sleep,但是还是返回了正确的值。不过只测试了一遍( 100 万个 sleep 实在是太慢了)。看看还有没有其他可能。
aijam
2019-05-31 04:01:12 +08:00
@tcdh sys.setswitchinterval(interval)
Set the interpreter ’ s thread switch interval (in seconds). This floating-point value determines the ideal duration of the “ timeslices ” allocated to concurrently running Python threads.

一秒太慢了
binux
2019-05-31 04:10:03 +08:00
@tcdh 在 func 里面循环一百万遍啊
denonw
2019-05-31 09:33:03 +08:00
在函数里面做这个循环,不用这么多线程
suconghou
2019-05-31 10:39:20 +08:00
```
# encoding: utf-8

import threading
import time

d = {1:0}

def func():
for _ in range(10):
d[1] += 1
time.sleep(0.01)
d[1] += 1


threads = []
for _ in range(1000):
t = threading.Thread(target=func)
threads.append(t)
t.start()

for thread in threads:
thread.join()

print(d)

```

python2 下有问题

python3 上没问题

https://www.reddit.com/r/Python/comments/4vyg2m/threadinglocking_is_4x_as_fast_on_python_3_vs/d62giao/
tcdh
2019-05-31 10:58:00 +08:00
@suconghou 我又试了下,确实在 python2 下是有问题的,比较了下 func 的 bytecode 也不一样。反正 bytecode 这种东西也没有标准,说变就变,那就不钻牛角尖了。感谢。

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

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

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

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

© 2021 V2EX