如何在Python应用程序中检测数据竞争?

问题描述

尚不清楚使用哪些工具来检测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