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

我对社区的问题是:

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


当前回答

考虑一个操作,该操作必须在计数增加时立即显示计数。ie。,只要CounterThread增加值,DisplayThread就需要显示最近更新的值。

int i = 0;

输出

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

在这里,CounterThread频繁地获取锁,并在DisplayThread显示它之前更新值。这里存在一个竞态条件。竞争条件可以通过使用同步来解决

其他回答

什么是竞态条件?

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

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

你如何发现它们?

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

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

你是如何处理的?

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

你如何防止它们的发生?

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

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

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

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

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

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

一个有点规范的定义是“当两个线程同时访问内存中的同一个位置,并且至少有一次访问是写操作。”在这种情况下,“reader”线程可能获得旧值或新值,这取决于哪个线程“赢得了比赛”。这并不总是一个bug——事实上,一些非常复杂的低级算法会故意这样做——但通常应该避免。@Steve Gury的例子很好地说明了这可能是个问题。

竞态条件是一种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被抢占时,这是非常罕见的,但它是有可能发生的。

当访问共享资源的多线程(或其他并行)代码可能以导致意外结果的方式访问共享资源时,就存在“竞争条件”。

举个例子:

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

如果你有5个线程同时执行这段代码,x的值最终不会是50,000,000。事实上,它会随着每一次运行而变化。

这是因为,为了让每个线程增加x的值,它们必须做以下事情:(显然是简化的)

Retrieve the value of x
Add 1 to this value
Store this value to x

任何线程都可以在任何时间处于此进程的任何步骤,并且当涉及共享资源时,它们可以相互踩。在读取x和写回x之间的时间内,x的状态可以由另一个线程改变。

假设一个线程获取了x的值,但还没有存储它。另一个线程也可以检索相同的x值(因为还没有线程更改它),然后它们都将在x中存储相同的值(x+1) !

例子:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, value is 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: stores 8 in x

竞争条件可以通过在代码访问共享资源之前使用某种锁定机制来避免:

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

这里,答案每次都是50,000,000。

有关锁的更多信息,请搜索:互斥量,信号量,临界区,共享资源。