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

我对社区的问题是:

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


当前回答

当两个或多个线程可以访问共享数据,并且它们试图同时更改数据时,就会发生竞态条件。因为线程调度算法可以在任何时候在线程之间交换,所以您不知道线程将尝试访问共享数据的顺序。因此,数据更改的结果依赖于线程调度算法,即两个线程都在“竞相”访问/更改数据。

当一个线程执行“检查-然后-行动”时,问题经常发生。“check”如果值是X,那么“act”做一些取决于值是X的事情),另一个线程在“check”和“act”之间对值做一些事情。例句:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

这一点是,y可以是10,也可以是任何值,这取决于在检查和执行之间是否有另一个线程改变了x。你根本不知道。

为了防止竞争条件的发生,您通常会在共享数据周围放置一个锁,以确保一次只有一个线程可以访问数据。这意味着:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

其他回答

考虑一个操作,该操作必须在计数增加时立即显示计数。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显示它之前更新值。这里存在一个竞态条件。竞争条件可以通过使用同步来解决

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

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

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

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

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

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

这个讨论中的许多答案解释了什么是竞态条件。我试图解释为什么这个术语在软件行业被称为竞争条件。

为什么叫竞态条件?

竞态条件不仅与软件有关,也与硬件有关。实际上,这个术语最初是由硬件行业创造的。

根据维基百科:

这个术语起源于两个信号相互竞争的想法 首先影响输出。 逻辑电路中的竞态:

软件行业没有对这个术语进行修改,这使得它有点难以理解。

你需要做一些替换来把它映射到软件世界:

"两个信号" ==> "两个线程"/"两个进程" "影响输出" ==> "影响一些共享状态"

因此,软件行业中的竞争条件是指“两个线程”/“两个进程”相互竞争以“影响某种共享状态”,而共享状态的最终结果将取决于一些微妙的时间差,而时间差可能是由某些特定的线程/进程启动顺序、线程/进程调度等引起的。