问题描述
尚不清楚使用哪些工具来检测python脚本,python代码或基于python的可执行文件中数据争用的可能发生。
我知道至少对于cpython,只允许对GIL执行一个线程。但这并不意味着不会发生数据争用(例如,当两个线程在结构上写入一个属性,每个都有一个值时)。
那我该如何发现这种情况?
解决方法
假设您已经完成了显而易见的第一步,并仔细研究了代码中的诸如共享变量v += 1
之类的非原子操作...
您可以通过增加线程丢弃GIL的频率来扩大显示竞争条件的窗口。您可以通过将切换间隔从默认值5 ms降低来实现。您还可以在代码中的可疑区域周围插入time.sleep(0)
,以使一个线程在该位置删除GIL(如果此时有人试图获取它)。另外,可以随机选择插入的睡眠时间(“模糊测试”)来进一步扭曲线程的执行顺序。
因为可能每个人都不清楚这样做的目的是什么
您的代码可能会工作,因为线程可以在5毫秒的标准时间段内轻松浏览关键部分,并且在测试过程中通过操作系统调度获得了“幸运”。后来,您添加了一些代码,关键部分突然位于时间片的边缘,并且您会通过怪异的bug注意到竞争状况。
在更改sys.setswitchinterval
的帮助下显示种族条件的演示代码:
from threading import Thread
import sys
# import time
def foo(n):
global global_v
for _ in range(n):
x = global_v
for _ in range(100): # expand race condition for demo
pass
# time.sleep(0) # also: multiple locations with randomizing sleep times
global_v = x + 1
def run(n_workers,n_iter):
print(f"test with switch-interval: {sys.getswitchinterval()} sec")
pool = [Thread(target=foo,args=(n_iter,)) for _ in range(n_workers)]
for t in pool:
t.start()
for t in pool:
t.join()
print(f"expected: {n_workers * n_iter},actual: {global_v}")
if __name__ == '__main__':
N_WORKERS = 8
n_iter = 1000
global_v = 0
# sys.setswitchinterval(0.001)
run(N_WORKERS,n_iter)
输出:
test with switch-interval: 0.005 sec
expected: 8000,actual: 8000
Process finished with exit code 0
输出为sys.setswitchinterval(0.001)
:
test with switch-interval: 0.001 sec
expected: 8000,actual: 7318
Process finished with exit code 0