Python中的数据竞争,尽管存在全局解释器锁

2022-02-22 06:42:01

我最近写了一篇关于语言如何改善生锈的文章#39;s和小马';我的一位读者问:

"Python是否具有无畏的并发性?全局解释器锁一次只运行一个线程,所以我';d假设有';没有数据竞赛"

注意我们';我们谈论的不是一般的比赛条件,只是数据比赛。数据竞赛是指:0

但在Python中,由于全局解释器锁,每个线程都是同步的。所以它可以';我们没有数据竞赛,对吗?

1在Python中,因为一次只能运行一个线程,所以它';这是同时发生的。并发性是指我们';我们一次完成多项任务,但一次只完成一项任务。并行性是指我们一次使用多个内核来处理多个线程。

起初我认为,虽然我们需要互斥来避免竞争条件,但GIL使Python不受数据竞争的影响。

然而,这并没有';似乎不对。我做了一点实验,制作了这个程序:

from threading import Threadfrom time import sleepcounter=0def increase():范围内i的全局计数器(0,100000):计数器=计数器+1线程=[]范围内i(0,400):线程。为线程中的线程追加(线程(目标=增加)):线程。线程中的线程的start():线程。join()打印(f';最终计数器:{counter}';)

这个项目';s数据竞争发生在计数器=计数器+1行。让';假设只有两个线程,程序才刚刚启动。

正如您所见,即使两个线程都递增计数器,它也会';不是2,而是39;s 1。这是一场正在进行的数据竞赛。

2这是在2.6 GHz 6核i7 Mac上,为了让您的计算机显示数据竞争,您可能需要更改400或100000。

3我说";注册号";,但CPython没有';不能直接使用寄存器;根据这篇文章,它将变量加载到存储在堆上的堆栈帧上。

这其实很难发现。前几次实验失败了,因为Python在运行每个线程时都非常聪明。

Python只会在以下情况下中断线程';时间太长了。来自anekix';答案是:

在新版本中,不再使用指令计数作为切换线程的指标,而是使用可配置的时间间隔。默认切换间隔为5毫秒。

因此,一个线程似乎需要持续5毫秒以上才能触发数据竞争。这解释了为什么我们必须在每个线程中进行100000次迭代。

这一政策使得人们很难确定何时存在数据竞争。然而,这可能也会减少它们在生产中的频率,这是一个很好的好处。

去吧';s映射的迭代顺序是随机的,部分原因是它防止我们意外地依赖迭代顺序。我们可以从中得到一些启发。

我想知道,在开发模式下,Python是否可以使用更短的时间间隔,以便我们注意到代码中隐藏的任何数据竞争,而在发布模式下,Python是否可以使用更长的时间间隔(5ms)。4.

依赖非保证决定论可能会导致一些错误。例如,如果我们意外地依赖于散列映射';s在100个位置排序,然后更改我们的哈希图#39;我们突然发现了100个错误。

这些似乎是不相容的,但淡水河谷的一个核心目标是使种族更加明显,并容易繁殖。

我们可以通过通用确定性可重放性来实现这一点,在开发模式中,我们记录所有非确定性输入,例如命令行参数、stdin、套接字、文件等。6 7,以及所有线程间消息和互斥锁的调度。这似乎很难,但它';例如,如果一种语言使用基于区域的借用检查器,并消除不安全的块和未定义的行为,那么它就有可能保证确定性。

这样一来,每一次跑步都是随机的,但都会被记录下来。当我们遇到一场比赛时,我们可以通过重放记录来重现这场比赛。

5 A";海森堡";就是当我们遇到一个错误,但在调查时,很难让它再次发生,因为它';它基于一些不确定因素。

7这个功能在Vale中已经完成了一半:FFI语义和FFI序列化已经准备好了,但我们没有';我还没有把它记录到文件中。

对于Python老手来说,这一切听起来都很明显,但这让我感到惊讶。也许这会让其他人感到惊讶!

感谢阅读!如果您想讨论更多内容,请访问r/Vale subreddit或Vale discord,如果您喜欢这些文章或希望支持我们的努力,请在GitHub上赞助我们!