问答中心分类: MULTITHREADING什么是竞态条件?
0
匿名用户 提问 36分钟 前

在编写多线程应用程序时,最常见的问题之一是竞态条件。
我对社区的问题是:

  • 什么是比赛条件?
  • 你如何检测它们?
  • 你如何处理它们?
  • 最后,如何防止它们发生?
MikeMB 回复 36分钟 前

我想提一下 – 在没有指定语言的情况下 – 这个问题的大部分内容都无法正确回答,因为在不同的语言中,定义、后果和防止它们的工具可能会有所不同。

MikeMB 回复 36分钟 前

@迈克MB。同意,除非在分析字节码执行时,就像 Race Catcher 所做的那样(参见这个线程stackoverflow.com/a/29361427/1363844)我们可以处理所有那些大约 62 种编译为字节码的语言(参见en.wikipedia.org/wiki/List_of_JVM_languages)

18 Answers
0
privatehuff 回答 36分钟 前

当访问共享资源的多线程(或其他并行)代码可能以导致意外结果的方式这样做时,就会出现“竞争条件”。
举个例子:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

如果您有 5 个线程同时执行此代码,则 x 的值最终不会是 50,000,000。事实上,它会随着每次运行而变化。
这是因为,为了让每个线程增加 x 的值,它们必须执行以下操作:(显然是简化的)

检索 x 的值 将此值加 1 将此值存储到 x

任何线程在任何时候都可以处于这个过程中的任何一步,当涉及到共享资源时,它们可以互相踩踏。在读取 x 和写回 x 之间的时间期间,另一个线程可以更改 x 的状态。
假设一个线程检索了 x 的值,但还没有存储它。另一个线程也可以检索相同的x 的值(因为还没有线程更改它),然后它们都将存储相同的值 (x+1) 回到 x!
例子:

线程 1:读取 x,值为 7 线程 1:将 1 加到 x,值现在为 8 线程 2:读取 x,值为 7线程 1:将 8 存储在 x 线程 2:将 1 加到 x,值现在是 8 线程 2:在 x 中存储 8

竞态条件可以通过采用某种形式来避免锁定访问共享资源的代码之前的机制:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

在这里,答案每次都是 50,000,000。
有关锁定的更多信息,请搜索:互斥量、信号量、临界区、共享资源。

jakobengblom2 回复 36分钟 前

jakob.engbloms.se/archives/65举一个程序来测试这些事情有多容易变坏的例子……它真的取决于你正在运行的机器的内存模型。

user4624979 回复 36分钟 前

非要停在1000万,怎么能到5000万?

Jon Skeet 回复 36分钟 前

@nocomprende:由 5 个线程一次执行相同的代码,如代码段下方所述…

user4624979 回复 36分钟 前

@JonSkeet你是对的,我混淆了 i 和 x。谢谢你。

Bharat Dodeja 回复 36分钟 前

实现单例模式的双重检查锁定就是防止竞争条件的一个例子。

0
Vishal Shukla 回答 36分钟 前

什么是竞态条件?

你计划下午 5 点去看电影。您在下午 4 点询问门票的可用性。代表说他们有空。您放松并在演出前 5 分钟到达售票窗口。我相信你可以猜到会发生什么:这是一个完整的房子。这里的问题在于检查和操作之间的持续时间。你4点询问,5点行动。与此同时,有人抢了票。这是一个竞争条件——特别是竞争条件的“检查然后行动”场景。

你如何检测它们?

宗教代码审查,多线程单元测试。没有捷径可走。很少有 Eclipse 插件出现在这方面,但还没有稳定的。

您如何处理和预防它们?

最好的办法是创建无副作用和无状态的函数,尽可能使用不可变。但这并不总是可能的。因此,使用 java.util.concurrent.atomic、并发数据结构、适当的同步和基于参与者的并发将有所帮助。
最好的并发资源是 JCIP。你还可以获得更多以上解释的细节在这里.

Asclepius 回复 36分钟 前

代码审查和单元测试是次要的,而不是对你的耳朵之间的流程进行建模,并减少对共享内存的使用。

Tom O. 回复 36分钟 前

我很欣赏竞争条件的真实世界示例

Volt 回复 36分钟 前

喜欢答案竖起大拇指.解决方案是:您使用互斥锁(互斥,c++)将票证锁定在 4-5 之间。在现实世界中,它被称为订票:)

Corey Goldberg 回复 36分钟 前

如果您放弃了仅限 java 的位,那将是一个不错的答案(问题不是关于 Java,而是一般的竞争条件)

csherriff 回复 36分钟 前

不,这不是比赛条件。从“业务”的角度来看,您等待的时间太长了。显然,延期交货不是解决方案。尝试黄牛,否则只需购买门票作为保险

Sumanth Varada 回复 36分钟 前

确实很好的答案,如果我们遇到“CHECK AND ACT”和“READ_MODIFY_UPDATE”情况,就会出现竞争条件

John 回复 36分钟 前

很好的例子! +1

John 回复 36分钟 前

@Volt 订票?那是什么?

0
Baris Kasikci 回答 36分钟 前

竞争条件和数据竞争之间存在重要的技术差异。大多数答案似乎都假设这些术语是等价的,但事实并非如此。
当 2 条指令访问相同的内存位置时,会发生数据竞争,这些访问中至少有一个是写入,并且没有在订购之前发生在这些访问中。现在,什么构成了在排序之前发生的事情有很多争论,但一般来说,同一锁变量上的 ulock-lock 对和同一条件变量上的等待信号对会导致发生前发生的顺序。
竞争条件是语义错误。它是发生在时间或事件顺序中的缺陷,导致错误的程序行为.
许多竞争条件可能(实际上是)由数据竞争引起,但这不是必需的。事实上,数据竞争和竞争条件既不是彼此的必要条件,也不是充分条件。This博客文章也很好地解释了差异,并以一个简单的银行交易示例。这是另一个简单的例子这解释了差异。
现在我们确定了术语,让我们尝试回答最初的问题。
鉴于竞争条件是语义错误,因此没有检测它们的通用方法。这是因为在一般情况下,没有办法拥有可以区分正确和不正确程序行为的自动预言机。种族检测是一个不可判定的问题。
另一方面,数据竞争有一个精确的定义,不一定与正确性相关,因此可以检测到它们。有多种数据竞争检测器(静态/动态数据竞争检测、基于锁集的数据竞争检测、基于发生前的数据竞争检测、混合数据竞争检测)。最先进的动态数据竞争检测器是ThreadSanitizer这在实践中效果很好。
处理数据竞争通常需要一些编程规则来诱导访问共享数据之间的先发生边缘(在开发期间,或者一旦使用上述工具检测到它们)。这可以通过锁、条件变量、信号量等来完成。但是,也可以采用不同的编程范式,如消息传递(而不是共享内存),通过构造来避免数据竞争。

ProgramCpp 回复 36分钟 前

差异对于了解比赛条件至关重要。谢谢!

truefusion 回复 36分钟 前

“这是一个发生在时间或事件顺序中的缺陷,会导致错误的程序行为。”完美的定义!事实上,没有理由假设事件必须发生在应用程序的一个实例中。多个实例同样适用。

0
Chris Conway 回答 36分钟 前

一种规范的定义是“当两个线程同时访问内存中的同一位置,并且至少其中一个访问是写入时.” 在这种情况下,“阅读器”线程可能会得到旧值或新值,这取决于哪个线程“赢得比赛”。这并不总是一个错误——事实上,一些非常棘手的低级算法会这样做目的 – 但通常应该避免。@Steve Gury 给出了一个很好的例子,说明它何时可能是一个问题。

Alex V. 回复 36分钟 前

您能否举例说明竞争条件如何有用?谷歌搜索没有帮助。

Chris Conway 回复 36分钟 前

@Alex V. 在这一点上,我不知道我在说什么。我认为这可能是对无锁编程的参考,但说这取决于竞争条件本身并不准确。

0
Jorge Córdoba 回答 36分钟 前

竞争条件是并发编程中的一种情况,其中两个并发线程或进程竞争资源,最终状态取决于谁首先获得资源。

gokareless 回复 36分钟 前

只是精彩的解释

Roman Alexandrovich 回复 36分钟 前

最终状态是什么?

Alexander Terp 回复 36分钟 前

@RomanAlexandrovich 程序的最终状态。状态指的是变量值等事物。请参阅 Lehane 的出色回答。他示例中的“状态”指的是“x”和“y”的最终值。