程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 【C#進階系列】29 混合線程同步構造,

【C#進階系列】29 混合線程同步構造,

編輯:C#入門知識

【C#進階系列】29 混合線程同步構造,


上一章講了基元線程同步構造,而其它的線程同步構造都是基於這些基元線程同步構造的,並且一般都合並了用戶模式和內核模式構造,我們稱之為混合線程同步構造。

在沒有線程競爭時,混合線程提供了基於用戶模式構造所具備的性能優勢,而多個線程競爭一個構造時,混合線程通過基元內核模式的構造來提供不“自旋”的優勢。

那麼接下來就是個簡單的混合線程同步構造的例子,可與上一章最後的那些例子相比較:

   public class SimpleHybridLock : IDisposable {
        private Int32 m_waiters = 0;

        private AutoResetEvent m_waiterlock = new AutoResetEvent(false);//注意這裡是false

        public void Enter() {
            if (Interlocked.Increment(ref m_waiters)==1) {
                return;
            }
            m_waiterlock.WaitOne();
        }
        public void Leave() {
            if (Interlocked.Decrement(ref m_waiters) == 0) {
                return;
            }
            m_waiterlock.Set();
        }
        public void Dispose() {
            m_waiterlock.Dispose();
        }
    }

上面的例子學了上一張後看起來感覺很簡單就不講解了,只是一個簡單的,將Interlocked這種互鎖構造和自動重置事件構造AutoResetEvent 結合起來的,混合線程同步構造的例子。

上面混合鎖可以去加入自旋,當超過一定的自旋次數時再進行阻塞。也可以去加入互斥體的遞歸玩法,總之這個東西充滿了無限的可能。

.NET 框架類庫中的混合構造

總體而言,實際上就是對上面那個簡單例子的擴展,它們的目的都是為了使線程能盡可能不去進入內核模式,並且減少線程競爭時自旋的性能影響。

  • ManualResetEventSlim類和SemaphoreSlim類
    • 翻譯過來就是手工重置事件簡化構造和信號量簡化構造
    • 發生第一次競爭時才進行內核模式構造,否則為用戶模式構造
    • 可傳遞超時值和CancellationToken,也就是取消啦,信號量那個還能進行異步等待。
  • Monitor類和同步塊
    • Monitor類是最常用的,支持遞歸,線程所有權和互斥
    • 然而這個類存在一些問題,容易引發BUG。因為它是一個靜態類,它的正確玩法在一定程度上和其它同步構造有所區別。
    • 堆中的每個對象都可以關聯一個叫同步塊的數據結構,它為內核對象,且擁有線程ID,遞歸計數,等待線程計數。而Monitor類的操作就涉及到這些同步塊的字段。
    • 每個對象都有一個同步塊索引,而同步塊實際上是在CLR初始化的時候就創建的一個同步塊數組中。
    • 一個對象在構造時它的同步塊索引為-1,就是沒有關聯任何同步塊。而調用Monitor.Enter後CLR在同步塊數組中找到個空白同步塊,並設置對象的同步塊索引,讓它引用該同步塊。Exit當然就是取消關聯。
    • Monitor.Enter會傳一個對象進去,這個對象必須為所在函數的類的私有對象,而不能傳所在對象本身,這回讓這個鎖變成公共的。這樣就會引發很多問題。所以最好的方法就是傳遞一個私有的只讀對象。
    • 永遠不要講String,值類型和類型對象傳給Monitor.Enter。
    • 而C#有一個lock關鍵字提供的簡化語法就是基於Monitor的。而且其相當於在一個try finally結構上使用。首先不利於性能,其次還可能造成線程訪問損壞的狀態。所以作者建議杜絕使用lock語法。
    • LockToken變量默認false,只有在Enter調用後才為true,要是在Enter調用前Exit,可以考慮判斷LockToken,從而避免錯誤的Exit。
  • ReaderWriterLockSlim類
    • 它的特點:
      • 一個線程向數據寫入時,請求訪問的其他所有線程都被阻塞
      • 一個線程從數據讀取時,請求讀取的其它線程允許繼續執行,但請求寫入的線程仍被阻塞。
      • 向線程寫入的線程結束後,要麼解除一個寫入線程的阻塞,使它能向數據寫入,要麼解除所有讀取線程的阻塞,使它們能並發讀取數據。如果沒有線程被阻塞,鎖就進入可以自由使用的狀態,可供下一個reader或writer線程獲取。
      • 從數據讀取所有線程結束後,一個writer線程被解除阻塞,使它能向數據寫入。如果沒有線程被阻塞,鎖就進入可以自由使用的狀態,可供下一個reader或writer線程獲取。
    •  根據以上特點有EnterReadLock和EnterWriteLock兩種玩法,兩種玩法跟之前的那些例子都類似,只是效果不同,這裡就不舉例了。

雖然提供了這麼多同步構造,且玩法也很多。但是最重要的還是一點:能盡量避免就避免阻塞線程,否則應盡量使用Volatile和Interlocked方法,因為它們速度快,然而這兩個只能操作簡單類型。

一定要阻塞,就可以使用Monitor類,也可以用ReaderWriterLockSlim類,雖然比Monitor慢,但是允許多個線程並發進行,提升了總體性能,減少阻塞線程的幾率。

用System.Lazy類或者System.Threading.LazyInitializer類去替代雙檢索玩法。

一句話解決這個點:

Lazy<String> s=new Lazy<String>(()=>DateTime.Now.ToLongTimeString(),true);

調用的話就用s.Value,實際上就是封裝了雙檢索,有些地方加了些優化。目的就是延時加載。

異步鎖

其實叫異步的同步構造,因為一般的同步構造都是用阻塞線程或者自旋來完成,而異步鎖的目的就是為了不阻塞來玩。

SemaphoreSlim類的WaitAsync方法就是這個思路,信號量玩法而已。

而reader-writer語義的玩法是ConcurrentExclusiveSchedulerPair類。(當沒有ConcurrentScheduler任務時,使用ExclusiveScheduler為獨占式運行。沒有ExclusiveScheduler運行時,ConcurrentScheduler調度的任務可同時進行)

並發集合類

FCL自帶四個線程安全的集合類,全在System.Collections.Concurrent(Concurrent為並發的意思)命名空間中定義。

它們是ConcurrentQueue,ConcurrentStack,ComcurrentDictionary和ConcurrentBag。

所有這些都是“非阻塞“的。(實際上在ConcurrentQueue,ConcurrentStack和ConcurrentBag為空的時候還要提取數據,那麼提取數據的這個線程就會被阻塞)

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved