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

我对社区的问题是:

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


当前回答

您并不总是希望丢弃竞态条件。如果你有一个可以被多个线程读写的标志,并且这个标志被一个线程设置为“done”,这样当标志被设置为“done”时,其他线程就会停止处理,你不希望这个“竞争条件”被消除。事实上,这可以被称为良性竞态条件。

然而,使用检测竞态条件的工具,它将被视为有害的竞态条件。

更多关于比赛情况的详细信息,请访问http://msdn.microsoft.com/en-us/magazine/cc546569.aspx。

其他回答

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

为什么叫竞态条件?

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

根据维基百科:

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

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

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

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

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

如果你使用“原子”类,你可以防止竞争条件。原因是线程没有分开操作get和set,示例如下:

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

因此,你将有7在链接“ai”。 虽然你做了两个操作,但这两个操作都确认了同一个线程,没有其他线程会干涉这,这意味着没有竞争条件!

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

举个例子:

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。

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

竞态条件是当两个或多个进程可以同时访问和更改共享数据时出现的不希望出现的情况。这是因为对资源的访问发生冲突。临界区问题可能导致竞争状态。为了解决进程之间的临界条件,我们每次只取出一个执行临界段的进程。

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

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.