C#Threading:竞争条件示例

我正在阅读http://www.mono-project.com/ThreadsBeginnersGuide 。

第一个示例如下所示:

public class FirstUnsyncThreads { private int i = 0; public static void Main (string[] args) { FirstUnsyncThreads myThreads = new FirstUnsyncThreads (); } public FirstUnsyncThreads () { // Creating our two threads. The ThreadStart delegate is points to // the method being run in a new thread. Thread firstRunner = new Thread (new ThreadStart (this.firstRun)); Thread secondRunner = new Thread (new ThreadStart (this.secondRun)); // Starting our two threads. Thread.Sleep(10) gives the first Thread // 10 miliseconds more time. firstRunner.Start (); Thread.Sleep (10); secondRunner.Start (); } // This method is being excecuted on the first thread. public void firstRun () { while(this.i < 10) { Console.WriteLine ("First runner incrementing i from " + this.i + " to " + ++this.i); // This avoids that the first runner does all the work before // the second one has even started. (Happens on high performance // machines sometimes.) Thread.Sleep (100); } } // This method is being excecuted on the second thread. public void secondRun () { while(this.i < 10) { Console.WriteLine ("Second runner incrementing i from " + this.i + " to " + ++this.i); Thread.Sleep (100); } } } 

输出:

 First runner incrementing i from 0 to 1 Second runner incrementing i from 1 to 2 Second runner incrementing i from 3 to 4 First runner incrementing i from 2 to 3 Second runner incrementing i from 5 to 6 First runner incrementing i from 4 to 5 First runner incrementing i from 6 to 7 Second runner incrementing i from 7 to 8 Second runner incrementing i from 9 to 10 First runner incrementing i from 8 to 9 

哇,这是什么? 不幸的是,文章中的解释对我来说不够。 你能解释一下为什么增量发生在混乱的顺序中吗?

谢谢!

我认为文章的作者混淆了一些事情。

VoteyDisciple是正确的, ++i不是primefaces的,如果目标在操作期间没有被锁定,则可能发生竞争条件,但这不会导致上述问题。

如果调用++i发生竞争条件,那么++运算符的内部操作将类似于: –

  1. 第一个线程读取值0
  2. 第二个线程读取值0
  3. 第1个线程将值递增为1
  4. 第二个线程将值增加到1
  5. 第一个线程写入值1
  6. 第二个线程写入值1

操作3到6的顺序并不重要,重点是读取操作1和2都可以在变量具有值x时产生与y相同的增量,而不是每个线程对x的不同值执行递增和y。

这可能会导致以下输出: –

 First runner incrementing i from 0 to 1 Second runner incrementing i from 0 to 1 

更糟糕的是以下几点: –

  1. 第一个线程读取值0
  2. 第二个线程读取值0
  3. 第二个线程将值增加到1
  4. 第二个线程写入值1
  5. 第二个线程读取值1
  6. 第二个线程将值增加到2
  7. 第二个线程写入值2
  8. 第1个线程将值递增为1
  9. 第一个线程写入值1
  10. 第二个线程读取值1
  11. 第二个线程将值增加到2
  12. 第二个线程写入值2

这可能会导致以下输出: –

 First runner incrementing i from 0 to 1 Second runner incrementing i from 0 to 1 Second runner incrementing i from 1 to 2 Second runner incrementing i from 1 to 2 

等等。

此外,由于Console.WriteLine调用连接i++i ,因此在读取i和执行++i之间可能存在竞争条件。 这可能会产生如下输出: –

 First runner incrementing i from 0 to 1 Second runner incrementing i from 1 to 3 First runner incrementing i from 1 to 2 

编写器描述的混乱的控制台输出只能由控制台输出的不可预测性导致,并且与i变量的竞争条件无关。 在执行++i或同时连接i++i锁定i ++i将不会改变此行为。

当我运行它(在双核上)时,我的输出是

 First runner incrementing i from 0 to 1 Second runner incrementing i from 1 to 2 First runner incrementing i from 2 to 3 Second runner incrementing i from 3 to 4 First runner incrementing i from 4 to 5 Second runner incrementing i from 5 to 6 First runner incrementing i from 6 to 7 Second runner incrementing i from 7 to 8 First runner incrementing i from 8 to 9 Second runner incrementing i from 9 to 10 

正如我所料。 您正在运行两个循环,都执行Sleep(100)。 这非常不适合展示竞争条件。

代码确实有竞争条件(如VoteyDisciple描述的那样),但它不太可能浮出水面。

我无法解释输出中缺少顺序(它是真正的输出吗?),但Console类将同步输出调用。

如果省略Sleep()调用并运行循环1000次(而不是10次),您可能会看到两个运行器从554增加到555或其他东西。

当存在多个线程时,同步是必不可少的。 在这种情况下,您会看到两个线程都读取和写入this.i ,但在同步这些访问时没有做出任何好的尝试。 由于它们同时修改了相同的内存区域,因此您会观察到混乱的输出。 睡眠是危险的,这是一种导致确定错误的方法。 你不能假设螺纹总是被初始位移10毫秒。

简而言之:永远不要使用Sleep进行同步:-)而是采用某种线程同步技术(例如,锁,互斥,信号量)。 始终尝试使用最轻的锁,以满足您的需求….

一个有用的资源是Joe Duffy的书,Windows上的Concurrent Programming。

增量不会乱序发生,Console.WriteLine(…)将多个线程的输出写入单线程控制台,从多个线程到一个线程的同步导致消息出现故障。

我假设这个例子试图创建竞争条件,并且在你的情况下失败了。 不幸的是,由于其性质,并发问题(例如竞争条件和死锁)难以预测和重现。 您可能想尝试再运行几次,更改它以使用更multithreading,每个线程应增加更多次(比如100,000)。 然后你可能会看到最终结果不等于所有增量的总和(由竞争条件引起)。