1. 程式人生 > >C# 多線程之線程同步

C# 多線程之線程同步

嘗試 alt 指定 summary rpm semaphore spi 句柄 star



多線程間應盡量避免同步問題,最好不要線程間共享數據。如果必須要共享數據,就需要使用同步技術,確保一次只有一個線程訪問和改變共享狀態。

一::lock語句

lock語句事設置鎖定和接觸鎖定的一種簡單方法。其語法非常簡單:

            lock (obj)
            {
                // 需要發生同步的代碼區
            }

將共享數據的操作代碼,放在上述的“{...}”區域內。鎖定的對象(obj)必須是引用類型,如果鎖定一個值類型,實際是鎖定了它的一個副本,並沒有實現鎖定功能。

一般地,被鎖定對象需要被創建為 私有 只讀 引用類型:

        private
readonly object obj = new object();

二::Interlocked類

Interlocked類用於使變量的簡單語句原子化。它提供了以線程安全的方式遞增、遞減、交換和讀取值的方法。

        private int stateFlag = 0;

        public int IncrementState
        {
            //get
            //{
            //    lock (this)
            //    {
            //        stateFlag++;
            
// return stateFlag; // } //} get { return Interlocked.Increment(ref stateFlag); // using System.Threading; //Interlocked.Decrement(ref V0); //Interlocked.Exchange(ref V1, ref V2); //Interlocked.Read(ref V0);
} }

三::Monitor類

與lock相似,C#的lock語句被編譯器解析為使用Monitor類。鎖定開始相當於 Monitor.Enter(obj) 方法,該方法會一直等待,直到線程被對象鎖定。解除鎖定後線程進入同步階段,使用 Monitor.Exit(obj)方法解除鎖定,編譯器將它與try塊的finally結合。方法一中的代碼,相當於:

            Monitor.Enter(obj);
            try
            {
                // 需要發生同步的代碼區
            }
            finally
            {
                Monitor.Exit(obj);
            }

與lock語句相比,Monitor類的優點在於:可以添加一個等待北鎖定的超時值。這樣就不會無限期等待被鎖定,而可以使用 TryEnter() 方法,給一個超時參數。

            bool lockTaken = false;
            Monitor.TryEnter(obj, 500, ref lockTaken);
            if (lockTaken)
            {
                try
                {
                    // acquired the lock
                    // synchronized region for obj
                }
                finally
                {
                    Monitor.Exit(obj);
                }
            }
            else
            {
                // didn‘t get the lock,do something else
            }

如果obj被鎖定,TryEnter() 方法就會把 bool 型引用參數 lockTaken 設置為 true,並同步地訪問由 obj 鎖定的狀態。如果另一線程 鎖定 obj 的時間超過 500 毫秒,Try Enter() 方法就把變量 lockTaken 設為 false ,線程不再等待,而是用於執行其它操作。也許在之後,該線程會嘗試再次被鎖定。

四::SpinLock結構

它是一個結構體(struct),用法極類似於Monitor類。獲得鎖用 Enter()或TryEnter() 方法,釋放鎖用 Exit() 方法。它還提供了屬性 IsHeld 和 IsHeldByCurrentThred ,指定當前是否被鎖定。

        SpinLock mSpinLock = new SpinLock(); // 最好只是用一個 SpinLock

        public void fun1()
        {
            // .....

            bool lockTaken = false;
            mSpinLock.Enter(ref lockTaken);
            try
            {
                // synchronized region
            }
            finally
            {
                mSpinLock.Exit();
            }

            // ...
        }
        public void fun2()
        {
            // .....

            bool lockTaken = false;
            mSpinLock.TryEnter(500, ref lockTaken);
            if (lockTaken)
            {
                try
                {
                    // synchronized region
                }
                finally
                {
                    mSpinLock.Exit();
                }
            }
            else
            {
                // didn‘t get the lock,do something else
            }

            // ...
        }

SpinLock結構體是 .Net 4 新增。它適用於:有大量的鎖,且鎖定時間都非常短。程序需要避免使用多個 SpinLock 結構,也不要調用任何可能阻塞的內容。

五::WaitHandle 基類

WaitHandle是一個抽象基類,用於等待一個信號的設置。可以等待不同的信號,因為WaitHandle是一個基類,可以從中派生一些類。

        public delegate int TakesAWhileDelegate(int data, int ms); // 聲明委托
        public void Main()
        {
            TakesAWhileDelegate vTAwdl = TakesAWhile;
            IAsyncResult vAr = vTAwdl.BeginInvoke(1, 3000, null, null);
            while(true)
            {
                Console.Write(".");
                if (vAr.AsyncWaitHandle.WaitOne(300, false)) // 等待 vAr.AsyncWaitHandle 收到信號(超時300毫秒)
                {
                    Console.WriteLine("Can get the result now.");
                    break;
                }
            }
            int result = vTAwdl.EndInvoke(vAr);
            Console.WriteLine("Result:{0}", result);

Console.Read(); }
int TakesAWhile(int data, int ms) { Console.WriteLine("TakesAWhile started"); Thread.Sleep(ms); Console.WriteLine("TakesAWhile completed"); return ++data; }

技術分享

以上實例代碼,使用”異步委托", BeginInvoke() 方法返回一個實現了 IAsycResult接口的對象。使用 IAsycResult 接口,可以用AsycResult屬性訪問 WaitHandle 基類。在調用WaitOne()方法時,線程等待一個與等待句柄相關的信號。

使用 WaitHandle 類可以等待一個信號出現(WaitOne()方法)、等待必須發出信號的多個對象(WaitAll()方法)、或者等待多個對象中的一個(WaitAny()方法)。後兩者事WaitHandle類的靜態方法,接收一個WaitHandle參數數組。

六::Mutex類

Mutex(mutual exclusion,互斥)是 .NET Framework中提供跨多個進程同步訪問的一個類。所以,它常被用於“程序單一啟動控制”。

        /// <summary>
        /// 單一進程 檢查,如果已經運行一個進程,返回false,表示檢查不通過。否則返回true。
        /// </summary>
        /// <returns></returns>
        private bool RunOnceCheck()
        {
            bool vExist;
            Mutex nMutex = new Mutex(false, "SingletonWinAppMutex", out vExist);
            if (!vExist)
            {
                // 表示已經啟動一個了,應退出當前啟動
                return false;
            }
            return true;
        }

它非常類似於Monitor類,因為他們都只有一個線程能擁有鎖定。只有一個線程能獲得互斥鎖定,訪問受互斥保護的同步代碼區域。Mutex派生自基類WaitHandle,因此可以利用WaitOne()方法獲得互斥鎖定,在該過程中成為該互斥的擁有者。調用 ReleaseMutex()方法,釋放互斥。

            bool createdNew;
            Mutex mutex = new Mutex(false, "ProCSharpMutex", out createdNew);

            if (mutex.WaitOne())
            {
                try
                {
                    // synchronized region
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
            else
            {
                // some problem happened while waiting
            }

七::Semaphore類

Semaphore非常類似於互斥,其區別在於Semaphore可以同時由多個線程使用。它是一種計數互斥鎖定,可以定義允許同時訪問受其鎖定保護的資源的線程個數。它適用於:有許多可用資源,且只允許一定數量的線程訪問該資源。

八::Events類

它是一種可以在系統範圍內同步資源的方法。

九::Barrier類

它非常適用於其中工作有很多個任務分支且以後又需要合並工作的情況。

十::ReaderWriterLockSlim類

為了使鎖定機制允許鎖定多個讀取器(而不是一個寫入器)訪問某個資源,可以使用此類。它提供了一個鎖定功能,如果沒有寫入器鎖定資源,就允許多個讀取器訪問資源,但只能有一個寫入器鎖定該資源。

C# 多線程之線程同步