2019年3月1日 星期五

C# 執行緒(Thread)的Lock及Interlocked探討

同時啟動不同執行緒,但是對同一塊記憶體空間做內容的改變,可能會影響該內容的正確性(Race Condition),此時我們需要使用Lock來控制,當該執行緒執行時,其他執行緒無法使用這個被Lock的資源,以確保資料的正確性。另外C#也提供了一個Interlocked的方式,讓你能夠在Increment、Decrement、Add等基本數學操作不使用Lock的狀況下,保持結果正確,

以下測試的概念是對同一個記憶體空間做++和--,正確的結果應該為0,但若沒有Lock或Interlocked機制,不同執行緒會更改同一塊記憶體的值,譬如threadOne執行了++,尚未執行--,而threadTwo此時又執行++,這時候就會出現誤差,導致最終結果錯誤。

執行10次的結果,Counter每次都不一樣,但CounterWithLock及CounterNoLock則每次都為0。

以下為程式代碼:

using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            /** *******************
             * Thread的Lock機制
             * ********************/

            //沒有使用Lock
            var c = new Counter();
            var threadOne = new Thread(() => CountNumbers(c));
            var threadTwo = new Thread(() => CountNumbers(c));
            var threadThree = new Thread(() => CountNumbers(c));
            threadOne.Start();
            threadTwo.Start();
            threadThree.Start();
            threadOne.Join();
            threadTwo.Join();
            threadThree.Join();
            Console.WriteLine($"Total is {c.Count}");

            //使用Lock
            var c1 = new CounterWithLock();
            threadOne = new Thread(() => CountNumbers(c1));
            threadTwo = new Thread(() => CountNumbers(c1));
            threadThree = new Thread(() => CountNumbers(c1));
            threadOne.Start();
            threadTwo.Start();
            threadThree.Start();
            threadOne.Join();
            threadTwo.Join();
            threadThree.Join();
            Console.WriteLine($"Total is {c1.Count}");

            //使用Interlocked
            var c2 = new CounterNoLock();
            threadOne = new Thread(() => CountNumbers(c2));
            threadTwo = new Thread(() => CountNumbers(c2));
            threadThree = new Thread(() => CountNumbers(c2));
            threadOne.Start();
            threadTwo.Start();
            threadThree.Start();
            threadOne.Join();
            threadTwo.Join();
            threadThree.Join();
            Console.WriteLine($"Total is {c2.Count}");
        }

        static void CountNumbers(CountBase cnt)
        {
            for (int i = 0; i <= 100000; i++)
            {
                cnt.Increase();
                cnt.Descrease();
            }
        }
    }

    abstract class CountBase
    {
        public abstract void Increase();
        public abstract void Descrease();
    }
    class Counter : CountBase
    {
        private int _count;
        public int Count { get { return _count; } }
        public override void Increase()
        {
            _count++;
        }
        public override void Descrease()
        {
            _count--;
        }
    }
    class CounterWithLock : CountBase
    {
        private int _count;
        public int Count { get { return _count; } }
        private readonly object _SyncRoot = new Object();
        public override void Increase()
        {
              //lock的對象必須是object(但不要lock string),不能是int, long等數值型
              //若為public函數,不要lock(this),不然很容易造成deadlock
            lock (_SyncRoot)
            {
                _count++;
            }
        }
        public override void Descrease()
        {
            lock (_SyncRoot)
            {
                _count--;
            }
        }
    }
    class CounterNoLock : CountBase
    {
        private int _count;
        public int Count { get { return _count; } }
        public override void Increase()
        {
            Interlocked.Increment(ref _count);
        }
        public override void Descrease()
        {
            Interlocked.Decrement(ref _count);
        }
    }
}



沒有留言:

張貼留言