在不使用同步机制的情况下从2个不同的线程写入共享资源

时间:2011-07-17 20:14:34

标签: c# .net multithreading synchronization thread-safety

我有以下代码(用c#编写,但很容易翻译成您喜欢的语言......)

class Program
{
    static int sharedState = 0;

    static void Main(string[] args)
    {
        Thread t1 = new Thread(UpdateState);
        Thread t2 = new Thread(UpdateState);
        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
        Console.WriteLine("sharedState value is {0}.", sharedState);
    }

    static void UpdateState()
    {
        for (int i = 0; i < 10; i++)
            sharedState++;
    }
}

正如你们任何人都可以猜到的,这段代码会创建两个工作线程,将共享状态值增加一次10次。在访问和写入共享值“sharedState”时,代码没有任何同步机制(例如互斥,监视器或任何其他...),主线程正在等待两个工作者完成其工作(Join)。 有人可以向我解释一下这段代码的问题是什么,最后可能是'sharedState'的值是2吗? (我的一个朋友被问到这个问题,他们告诉他这是可能的,我们都不明白......) 顺便说一句 - 当我运行这段代码时,我每次都得20分......

4 个答案:

答案 0 :(得分:3)

线程的寿命不够长。如果这么短的循环,它们同时运行的几率很小,尤其是在多核机器上。当你启动第二个线程时,第一个线程已经完成。因为它只需要几分之一微秒,远远少于启动线程所需的时间。让它们循环至少一千万次以增加赔率并观看比赛。发布版本中我的机器上的输出:

  

sharedState值为13952221。

每次运行时都会显示不同的值。

答案 1 :(得分:2)

它不是线程安全的,因为

sharedState++;

在内部被解释为:

  • 将值从静态属性推送到评估堆栈
  • 增加评估堆栈中的值
  • 将静态属性中的值替换为评估堆栈中的值

现在,如果此操作不是原子/同步,则可以让更多线程同时从静态属性中获取值,但只会存储从最后一个线程进行的修改。

如果您想要线程安全增量,则应使用Interlocked.Increment

答案 2 :(得分:0)

确实存在问题。但是只有经常运行循环才能实现:

假设状态为1
线程a读取状态并得到1
线程a由操作系统暂停,b运行
线程b读取状态并得到1
线程b计算1 + 1并写入2
线程b读取2
线程b计算2 + 1并写入3
...
线程a计算1 + 1并写入2
执行时3个增量,状态从1变为2

但是你永远不会得到低于最低边界条件的结果。因此,如果你从0开始并且每个线程增加10次,那么无论发生什么,你总是会得到10或更高。

对于snychronisation: Interlocked.Increment(ref whatever)是你的朋友。它是使用汇编程序中的lock-prefix实现的。这是安全递增值的最有效方法。 使用CompareEcxchange可以执行稍微复杂的操作。但锁(某事)也非常有效。

答案 3 :(得分:0)

  1. 两个线程都读取变量,并获得值0。
  2. 线程1设法将变量递增9次。
  3. 线程2设法将变量递增一次。但它以前认为它是0,所以它的新值是1。
  4. 两个线程都读取变量,并获得值1.
  5. 线程2设法将变量递增9次。
  6. 线程1设法将变量递增一次。但它认为它之前是1,所以它的新值是2。
  7. 当然,这在实践中极不可能!