C#Threading:竞争条件示例

时间:2009-08-22 12:01:05

标签: c# multithreading

我正在阅读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
哇,这是什么?不幸的是,文章中的解释对我来说不够。你能解释一下为什么增量发生在混乱的顺序中吗?

谢谢!

4 个答案:

答案 0 :(得分:3)

我认为文章的作者混淆了一切。

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

如果调用++i发生竞争条件,那么++运算符的内部操作将如下所示: -

  1. 第一个线程读取值0
  2. 第二个线程读取值0
  3. 第一个线程将值增加到1
  4. 第二个线程将值增加到1
  5. 第一个线程写入值1
  6. 第二个线程写入值1
  7. 操作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
    9. 第一个线程写入值1
    10. 第二个帖子读取值1
    11. 第二个线程将值增加到2
    12. 第二个线程写入值2
    13. 这可能会导致以下输出: -

      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时或在连接++ii时锁定++i不会改变此行为。

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:2)

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

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或其他什么。

答案 3 :(得分:0)

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

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