在编写多线程应用程序时,遇到的最常见的问题之一是竞争条件。

我对社区的问题是:

竞态条件是什么? 你如何发现它们? 你是如何处理的? 最后,你如何防止它们的发生?


当前回答

什么是竞态条件?

过程严重依赖于其他事件的顺序或时间的情况。

例如, 处理器A和处理器B的执行都需要相同的资源。

你如何发现它们?

有一些工具可以自动检测竞态状态:

基于锁集的竞赛检查器 发生在种族检测之前 杂交种族检测

你是如何处理的?

竞争条件可以由互斥量或信号量处理。它们就像锁一样,允许进程根据特定的需求获取资源,以防止竞争。

你如何防止它们的发生?

防止竞争状态的方法有很多种,比如避免临界区。

没有两个进程同时在它们的关键区域内。(互斥) 没有对速度或cpu数量做任何假设。 没有进程运行在阻塞其他进程的关键区域之外。 没有进程需要永远等待才能进入临界区。(A等待B资源,B等待C资源,C等待A资源)

其他回答

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

微软实际上已经发布了一篇关于竞态条件和死锁的非常详细的文章。最概括的摘要是标题段:

A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable. Then the first thread and second thread perform their operations on the value, and they race to see which thread can write the value last to the shared variable. The value of the thread that writes its value last is preserved, because the thread is writing over the value that the previous thread wrote.

我做了一个视频来解释这个。

从本质上讲,它是当你有一个跨多个线程共享的状态,在一个给定状态的第一次执行完成之前,另一个执行开始,一个给定操作的新线程的初始状态是错误的,因为前一次执行还没有完成。

由于第二次执行的初始状态是错误的,因此计算结果也是错误的。因为最终第二次执行会用错误的结果更新最终状态。

你可以在这里查看。 https://youtu.be/RWRicNoWKOY

在竞争条件和数据竞争之间有一个重要的技术差异。大多数答案似乎假设这些术语是等价的,但事实并非如此。

当两条指令访问相同的内存位置时,就会发生数据竞争,其中至少有一个是写,并且在这些访问之间没有发生排序之前。现在,什么构成happens before顺序还存在很多争论,但一般来说,同一个锁变量上的ulock-lock对和同一个条件变量上的wait-signal对诱导的是happens before顺序。

竞态条件是语义错误。它是发生在事件的时间或顺序上的缺陷,会导致错误的程序行为。

许多竞态条件可能(事实上也是)是由数据竞态引起的,但这是不必要的。事实上,数据竞争和竞争条件既不是彼此的必要条件,也不是彼此的充分条件。这篇博客文章也用一个简单的银行交易例子很好地解释了两者的区别。下面是另一个简单的例子来解释这种区别。

既然我们已经确定了术语,让我们试着回答最初的问题。

由于竞争条件是语义错误,因此没有检测它们的通用方法。这是因为在一般情况下,不可能有一个自动的oracle来区分正确和不正确的程序行为。种族检测是一个无法确定的问题。

另一方面,数据竞争有一个精确的定义,它不一定与正确性有关,因此可以检测到它们。数据竞争检测器有很多种(静态/动态数据竞争检测、基于锁集的数据竞争检测、基于先于事件发生的数据竞争检测、混合数据竞争检测)。最先进的动态数据竞争检测器是ThreadSanitizer,它在实践中工作得非常好。

处理数据竞争通常需要一些编程规程来在访问共享数据之间的边之前诱导happens(在开发过程中,或者在使用上述工具检测到它们之后)。这可以通过锁、条件变量、信号量等来实现。但是,还可以使用不同的编程范例,例如消息传递(而不是共享内存),以避免构造造成的数据竞争。

竞态条件是一种bug,只会在特定的时间条件下发生。

例子: 假设您有两个线程,A和B。

在线程A中:

if( object.a != 0 )
    object.avg = total / object.a

线程B:

object.a = 0

如果线程A在检查完对象后被抢占。a不为空,B将执行a = 0,当线程a将获得处理器时,它将执行“除零”。

这个错误只发生在if语句之后的线程A被抢占时,这是非常罕见的,但它是有可能发生的。