日期:2014-05-18  浏览次数:20855 次

关于增加和减少整数值的线程安全方式的困惑!!求高手解惑
今天在MSDN的Interlocked.Increment 方法 (Int32) 的示例中看到两个变量经过“相同的”运算后,最后结果却不同,貌似理由是线程安全什么的 ,不太懂。现在把例子的代码贴出来了,请高手分析一下 为什么SafeInstanceCount和UnsafeInstanceCount两个变量的值不同??!! 我怎么感觉肯定相同呢!!唉 越来越发现自己编程学的好差!
例子如下:


C# code

下面的代码示例说明增加和减少整数值的线程安全方式。SafeInstanceCount 始终为 0。但是 UnsafeInstanceCount 不一定为 0,因为在增加和减少计数之间会出现争用条件。此效果在多处理器计算机上尤其明显。

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        Thread thread1 = new Thread(new ThreadStart(ThreadMethod));
        Thread thread2 = new Thread(new ThreadStart(ThreadMethod));
        thread1.Start();
        thread2.Start();
        thread1.Join();
        thread2.Join();

        // Have the garbage collector run the finalizer for each
        // instance of CountClass and wait for it to finish.
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine("UnsafeInstanceCount: {0}" +
            "\nSafeCountInstances: {1}",
            CountClass.UnsafeInstanceCount.ToString(),
            CountClass.SafeInstanceCount.ToString());
    }

    static void ThreadMethod()
    {
        CountClass cClass;

        // Create 100,000 instances of CountClass.
        for(int i = 0; i < 100000; i++)
        {
            cClass = new CountClass();
        }
    }
}

class CountClass
{
    static int unsafeInstanceCount = 0;
    static int   safeInstanceCount = 0;

    static public int UnsafeInstanceCount
    {
        get {return unsafeInstanceCount;}
    }

    static public int SafeInstanceCount
    {
        get {return safeInstanceCount;}
    }

    public CountClass()
    {
        unsafeInstanceCount++;
        Interlocked.Increment(ref safeInstanceCount);
    }

    ~CountClass()
    {
        unsafeInstanceCount--;
        Interlocked.Decrement(ref safeInstanceCount);
    }
}




------解决方案--------------------
++操作其实是两个过程,
a++等同于
a = a+1;
这里面需要先把a的值取出来,然后加1,然后再把值赋给a
对于CPU来说这是两个过程,把a的值读出来,放到寄存器,累加,再把寄存器里的值读出来,赋值给a。
如果这个过程被打断会发生什么呢,以下的两个a++在不同线程中
a= 0;
a++;
a++;
第一个a++进行到给a赋值之前被打断,此时寄存器里的值是1。然后执行第二个a++。第二个a++执行之后a的值是1,然后切换回第一个给a赋值的阶段,相当于把之前算好的1赋值给a,所以两次a++之后a的值竟然是1.

------解决方案--------------------
首先你一定得知道这个概念,在c#中绝大多数的操作都不是原子的。
即使像简单的变量自增操作 a++,也不是原子的。
即这个 a++的操作有可能不是在一个时间片中执行完成的,执行a++的操作可能会被其它线程抢占。
而在此之间它没有预见到其它线程可能已经修改了a本身的值

Interlocked.Increment(ref safeInstanceCount)操作,或lock就是为了解决这个问题的。
让该操作原子完成,不被其它线程抢占。