C# 原子操作

Interlocked.Increment 方法:让++成为原子操作;Interlocked.Decrement 方法让--成为原子操作。
什么叫原子操作呢。就是不会被别人打断,因为C#中的一个语句,编译成机器代码后会变成多个语句。
在多线程环境中,线程切换有可能会发生在这多个语句中间。使用Interlocked.Increment,Interlocked.Decrement 可以避免被打断,保证线程安全。

使用Interlocked.Increment 方法和Interlocked.Decrement 方法MSND例子:

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);
    }
}

不用原子操作例子

class Program
    {
        static void Main(string[] args)
        {
            for (int loop = 0; loop < 20; loop++)
            {
                sum = 0;
                Thread t1 = new Thread(Thread1);
                Thread t2 = new Thread(Thread2);
                t1.Start();
                t2.Start();

                t1.Join();
                t2.Join();
                Console.WriteLine("sum = " + sum);         // sum = 200000 ?
            }
        }

        static int sum;
        static void Thread1()
        {
            for (int i = 0; i < 100000; i++) sum++;
        }
        static void Thread2()
        {
            for (int i = 0; i < 100000; i++) sum++;
        }
    }

结果:

/*
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 192361
sum = 175155
sum = 200000
sum = 176024
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 200000
sum = 176322
*/
Why the sum is not always 200000?
The reason is that sum++ is not thread safe (see the possible problem).
That is the reason we need Interlocked.Increment(), which guarantees the sum is always 200000.

Thread1 (sum++)                   Thread2 (sum++)
--------------------------------------------------------------------
mov   EAX, dword ptr sum          .
inc   EAX                         .
.                                 mov   EAX, dword ptr sum           // load sum into a register
.                                 inc   EAX                          // increase it
.                                 mov   dword ptr sum, EAX           // save back
mov   dword ptr sum, EAX
--------------------------------------------------------------------

problem: two sum++ are called in different thread,
but the sum is incremented only once.
也就是说因为C#中的一个语句,编译成机器代码后会变成多个语句,线程不安全,sum++的第100次操作就被打断了,而在第200000次++操作结束后CPU才轮询到sum++的第100次操作,这时sum的值就是101,

posted on 2022-06-14 10:19  马什么梅  阅读(498)  评论(0编辑  收藏  举报

导航