C#线程:竞态条件示例

20 浏览
0 Comments

C#线程:竞态条件示例

我正在阅读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 () {
        // 创建两个线程。ThreadStart委托指向在新线程中运行的方法。
        Thread firstRunner = new Thread (new ThreadStart (this.firstRun));
        Thread secondRunner = new Thread (new ThreadStart (this.secondRun));
        // 启动两个线程。Thread.Sleep(10)使第一个线程多得到10毫秒的时间。
        firstRunner.Start ();
        Thread.Sleep (10);
        secondRunner.Start ();
    }
    // 此方法在第一个线程上执行。
    public void firstRun () {
        while(this.i < 10) {
            Console.WriteLine ("First runner incrementing i from " + this.i +
                              " to " + ++this.i);
            // 这样可以避免第一个线程在第二个线程开始之前完成所有工作。(在高性能机器上有时会发生。)
            Thread.Sleep (100);
        }
    }
    // 此方法在第二个线程上执行。
    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

哇,这是什么?不幸的是,文章中的解释对我来说不充分。你能解释一下为什么递增的顺序会混乱吗?

谢谢!

0
0 Comments

C#多线程编程中,当存在多个线程时,同步是必不可少的。在这个例子中,可以看到两个线程都读写了this.i,但没有进行良好的同步。由于它们同时修改了同一块内存区域,所以输出结果混乱。

在这段代码中,调用Sleep是危险的,这种方法会导致确定的错误。你不能假设线程总是会在初始10毫秒内被替换。

简而言之,永远不要使用Sleep进行同步 🙂 而是采用某种线程同步技术(例如锁、互斥体、信号量)。总是试图使用最轻量级的锁来满足你的需求...

Joe Duffy的书《Concurrent Programming on Windows》是一个很好的资源。

我认为Thread.Sleep()并不是用来尝试同步线程的。它只是用来使增量可观察(否则所有的增量都会一次性显示在控制台上),这有一个副作用,几乎可以降低竞争条件发生的机会。

是的,评论中说过了,但实际上它被用作一种粗略的同步技术,看看两个Start()调用之间的10毫秒间隔。在没有其他技术的情况下,我认为Sleep伪装了两个线程之间的同步。

+1 Joe Duffy的书,它是Windows并发编程的圣经。

0
0 Comments

C#多线程:竞争条件示例

当我在一个双核处理器上运行这段代码时,输出结果如下:

第一个运行者将 i 从 0 增加到 1
第二个运行者将 i 从 1 增加到 2
第一个运行者将 i 从 2 增加到 3
第二个运行者将 i 从 3 增加到 4
第一个运行者将 i 从 4 增加到 5
第二个运行者将 i 从 5 增加到 6
第一个运行者将 i 从 6 增加到 7
第二个运行者将 i 从 7 增加到 8
第一个运行者将 i 从 8 增加到 9
第二个运行者将 i 从 9 增加到 10

正如我所预期的那样。你运行了两个循环,都执行了 Sleep(100)。这对于演示竞争条件来说非常不合适。

这段代码确实存在竞争条件(正如 VoteyDisciple 所描述的),但很不可能暴露出来。

我无法解释你输出结果的无序性(这是真实的输出吗?),但 Console 类会同步输出调用。

如果你省略 Sleep() 调用,并且将循环运行 1000 次(而不是 10 次),你可能会看到两个运行者都将 i 从 554 增加到 555 或其他值。

我只是复制了文章中的代码,然而通过删除程序中的初始 sleep 并将线程的 sleep 降低到 20,我也能够获得类似混乱的输出。

你是对的,原因在于 Console 的同步机制。

0
0 Comments

C#多线程:竞争条件示例

在上述内容中,出现了一个竞争条件问题。文章的作者混淆了问题的本质。VoteyDisciple正确地指出++i不是原子操作,如果在操作期间没有对目标进行锁定,就会发生竞争条件,但这并不会导致上述描述的问题。

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

  1. 第一个线程读取值为0
  2. 第二个线程读取值为0
  3. 第一个线程将值增加到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
  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

此外,在读取i和执行++i之间存在可能的竞争条件,因为Console.WriteLine调用将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进行锁定也不会改变这种行为。

0