C#線程鎖使用全功略

來源:互聯網
上載者:User

前兩篇簡單介紹了線程同步lock,Monitor,同步事件EventWaitHandler,互斥體Mutex的基本用法,在此基礎上,我們對 它們用法進行比較,並給出什麼時候需要鎖什麼時候不需要的幾點建議。最後,介紹幾個FCL中安全執行緒的類,集合類的鎖定方式等,做為對線程同步系列的完善 和補充。
 

       1.幾種同步方法的區別

      lock和Monitor是.NET用一個特殊結構實現的,Monitor對象是完全託管的、完全可移植的,並且在作業系統資源要求方 面可能更為有效,同步速度較快,但不能跨進程同步。lock(Monitor.Enter和Monitor.Exit方法的封裝),主要作用是鎖定臨界區,使臨 界區代碼只能被獲得鎖的線程執行。Monitor.Wait和Monitor.Pulse用於線程同步,類似訊號操作,個人感覺使用比較複雜,容易造成死 鎖。

      互斥體Mutex和事件對象EventWaitHandler屬於核心對象,利用核心對象進行線程同步,線程必須要在使用者模式和核心模 式間切換,所以一般效率很低,但利用互斥對象和事件對象這樣的核心對象,可以在多個進程中的各個線程間進行同步。

      互斥體Mutex類似於一個接力棒,拿到接力棒的線程才可以開始跑,當然接力棒一次只屬於一個線程(Thread Affinity),如果這個線程不釋放接力棒(Mutex.ReleaseMutex),那麼沒辦法,其他所有需要接力棒啟動並執行線程都知道能等著看熱 鬧。

      EventWaitHandle 類允許線程通過發訊號互相通訊。 通常,一個或多個線程在 EventWaitHandle 上阻止,直到一個未阻止的線程調用 Set 方法,以釋放一個或多個被阻止的線程。

       2.什麼時候需要鎖定

      首先要理解鎖定是解決競爭條件的,也就是多個線程同時訪問某個資源,造成意想不到的結果。比如,最簡單的情況是,一個計數器,兩個線程 同時加一,後果就是損失了一個計數,但相當頻繁的鎖定又可能帶來效能上的消耗,還有最可怕的情況死結。那麼什麼情況下我們需要使用鎖,什麼情況下不需要 呢?

      1)只有共用資源才需要鎖定
      只有可以被多線程訪問的共用資源才需要考慮鎖定,比如靜態變數,再比如某些緩衝中的值,而屬於線程內部的變數不需要鎖定。 

      2)多使用lock,少用Mutex
      如果你一定要使用鎖定,請盡量不要使用核心模組的鎖定機制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用這樣的機制涉及到了系統在使用者模式和核心模式間的切換,效能差很多,但是他們的優點是可以跨進程同步線程,所以應該清 楚的瞭解到他們的不同和適用範圍。

      3)瞭解你的程式是怎麼啟動並執行
      實際上在web開發中大多數邏輯都是在單個線程中展開的,一個請求都會在一個單獨的線程中處理,其中的大部分變數都是屬於這個線程的,根本沒有必要考慮鎖 定,當然對於ASP.NET中的Application對象中的資料,我們就要考慮加鎖了。

      4)把鎖定交給資料庫
      數 據庫除了儲存資料之外,還有一個重要的用途就是同步,資料庫本身用了一套複雜的機制來保證資料的可靠和一致性,這就為我們節省了很多的精力。保證了資料來源 頭上的同步,我們多數的精力就可以集中在緩衝等其他一些資源的同步訪問上了。通常,只有涉及到多個線程修改資料庫中同一條記錄時,我們才考慮加鎖。 

      5)商務邏輯對事務和安全執行緒的要求
      這 條是最根本的東西,開發完全安全執行緒的程式是件很費時費力的事情,在電子商務等涉及金融系統的案例中,許多邏輯都必須嚴格的安全執行緒,所以我們不得不犧牲 一些效能,和很多的開發時間來做這方面的工作。而一般的應用中,許多情況下雖然程式有競爭的危險,我們還是可以不使用鎖定,比如有的時候計數器少一多一, 對結果無傷大雅的情況下,我們就可以不用去管它。

      3.InterLocked類

      Interlocked 類提供了同步對多個線程共用的變數的訪問的方法。如果該變數位於共用記憶體中,則不同進程的線程就可以使用該機制。互鎖操作是原子的,即整個操作是不能由相 同變數上的另一個互鎖操作所中斷的單元。這在搶先多線程作業系統中是很重要的,在這樣的作業系統中,線程可以在從某個記憶體位址載入值之後但是在有機會更改 和儲存該值之前被掛起。

      我們來看一個InterLock.Increment()的例子,該方法以原子的形式遞增指定變數並儲存結果,樣本如下:


    class InterLockedTest
    {
        public static Int64 i = 0;

        public static void Add()
        {
            for (int i = 0; i < 100000000; i++)
            {
                Interlocked.Increment(ref InterLockedTest.i);
                //InterLockedTest.i = InterLockedTest.i + 1;
            }
        }

        public static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(InterLockedTest.Add));
            Thread t2 = new Thread(new ThreadStart(InterLockedTest.Add));

            t1.Start();
            t2.Start();

            t1.Join();
            t2.Join();

            Console.WriteLine(InterLockedTest.i.ToString());
            Console.Read();
        }
    }

      輸出結果200000000,如果InterLockedTest.Add()方法中用注釋掉的語句代替Interlocked.Increment() 方法,結果將不可預知,每次執行結果不同。InterLockedTest.Add()方法保證了加1操作的原子性,功能上相當於自動給加操作使用了 lock鎖。同時我們也注意到InterLockedTest.Add()用時比直接用+號加1要耗時的多,所以說加鎖資源損耗還是很明顯的。

      另外InterLockedTest類還有幾個常用方法,具體用法可以參考MSDN上的介紹。

    4.集合類的同步

      .NET在一些集合類,比如Queue、ArrayList、HashTable和Stack,已經提供了一個供lock使用的對象SyncRoot。用 Reflector查看了SyncRoot屬性(Stack.SynchRoot略有不同)的源碼如下:


public virtual object SyncRoot
{
    get
    {
        if (this._syncRoot == null)
        {
            //如果_syncRoot和null相等,將new object賦值給 _syncRoot
            //Interlocked.CompareExchange方法保證多個線程在使用 syncRoot時是安全執行緒的
            Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
        }
        return this._syncRoot;
    }
}

      這裡要特別注意的是MSDN提到:從頭到尾對一個集合進行枚舉本質上並不是一個安全執行緒的過程。即使一個集合已進行同步,其他線程仍可以修改該集合,這將 導致枚舉數引發異常。若要在枚舉過程中保證安全執行緒,可以在整個枚舉過程中鎖定集合,或者捕捉由於其他線程進行的更改而引發的異常。應該使用下面的代碼:

Queue使用lock樣本
    Queue q = new Queue();
    lock (q.SyncRoot)
    {
        foreach (object item in q)
        {
            //do something
        }
    }

      還有一點需要說明的是,集合類提供了一個是和同步相關的方法Synchronized,該 方法返回一個對應的集合類的wrapper類,該類是安全執行緒的,因為他的大部分方法都用lock關鍵字進行了同步處理。如HashTable的 Synchronized返回一個新的安全執行緒的HashTable執行個體,代碼如下:


    //在多線程環境中只要我們用下面的 方式執行個體化HashTable就可以了
    Hashtable ht = Hashtable.Synchronized(new Hashtable());

    //以下代碼是.NET Framework Class Library實現,增加對 Synchronized的認識
    [HostProtection(SecurityAction.LinkDemand, Synchronization=true)]
    public static Hashtable Synchronized(Hashtable table)
    {
        if (table == null)
        {
            throw new ArgumentNullException("table");
        }
        return new SyncHashtable(table);
    }

 
    //SyncHashtable的幾個常用方法,我們可以看到內部實現都加了lock關鍵字 保證安全執行緒
    public override void Add(object key, object value)
    {
        lock (this._table.SyncRoot)
        {
            this._table.Add(key, value);
        }
    }

    public override void Clear()
    {
        lock (this._table.SyncRoot)
        {
            this._table.Clear();
        }
    }

    public override void Remove(object key)
    {
        lock (this._table.SyncRoot)
        {
            this._table.Remove(key);
        }
    }

      線程同步是一個非常複雜的話題,這裡只是根據公司的一個項目把相關的知識整理出來,作為工作的一種總結。這些同步方法的使用情境是怎樣的?究竟有哪些細微 的差別?還有待於進一步的學習和實踐。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.