1、lock 、Monitor 算法
處理並行任務的時候,效率最高的就是多線程。當不一樣線程須要訪問同一資源時候,就須要同步了。就像生活中不少人要一塊兒趕飛機你們都要訪問飛機這個資源每一個人是一條線程那麼就有門,有了門就表明每次只能一位其餘人都要排隊進入。數據庫
Monitor 類控制對對象的訪問經過授予爲單線程的對象的鎖。對象鎖提供的功能來限制對代碼一般稱爲關鍵節的塊的訪問。一個線程擁有對象的鎖,而沒有其餘線程能夠獲取該鎖。您還可使用 Monitor 類,以確保沒有其餘線程有權訪問的應用程序一部分的代碼正在執行的鎖的全部者,除非其餘線程正在執行使用不一樣的鎖定的對象的代碼。編程
lock(object obj) //監視obj 是否有被使用或者lock,若是沒有就有我使用,不然一直等待obj被釋放。 { ...... }//釋放obj lock(this) //鎖住當前對象 將會鎖住當前類的實例 不能在靜態資源中使用 { ...... }//釋放this
lock 就是下面代碼的語法糖api
try
{ Monitor.Enter(this); XXX你的代碼 } finally { Monitor.Exit(this); }
demo緩存
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; public class Example { public static void Main() { List<Task> tasks = new List<Task>(); Random rnd = new Random(); long total = 0; int n = 0; for (int taskCtr = 0; taskCtr < 10; taskCtr++) tasks.Add(Task.Run( () => { int[] values = new int[10000]; int taskTotal = 0; int taskN = 0; int ctr = 0; Monitor.Enter(rnd); // Generate 10,000 random integers for (ctr = 0; ctr < 10000; ctr++) values[ctr] = rnd.Next(0, 1001); Monitor.Exit(rnd); taskN = ctr; foreach (var value in values) taskTotal += value; Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})", Task.CurrentId, (taskTotal * 1.0)/taskN, taskN); Interlocked.Add(ref n, taskN); Interlocked.Add(ref total, taskTotal); } )); try { Task.WaitAll(tasks.ToArray()); Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})", (total * 1.0)/n, n); } catch (AggregateException e) { foreach (var ie in e.InnerExceptions) Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message); } } } // The example displays output like the following: // Mean for task 1: 499.04 (N=10,000) // Mean for task 2: 500.42 (N=10,000) // Mean for task 3: 499.65 (N=10,000) // Mean for task 8: 502.59 (N=10,000) // Mean for task 5: 502.75 (N=10,000) // Mean for task 4: 494.88 (N=10,000) // Mean for task 7: 499.22 (N=10,000) // Mean for task 10: 496.45 (N=10,000) // Mean for task 6: 499.75 (N=10,000) // Mean for task 9: 502.79 (N=10,000) // // Mean for all tasks: 499.75 (N=100,000)
2、ReaderWriterLock 類安全
定義支持單個寫線程和多個讀線程的鎖。Microsoft 官方不推薦使用該類。Jeffrey Richter也在他的《CLR via C#》一書中對它進行了嚴厲的批判。多線程
性能:這個類實在是太慢了。好比它的 AcquireReaderLock 方法比 Monitor 類的 Enter 方法要慢 5 倍左右,而等待爭奪寫鎖甚至比 Monitor 類慢 6 倍。架構
策略:假如某個線程完成寫入操做後,同時面臨讀線程和寫線程等待處理。ReaderWriterLock會優先釋放讀線程,而讓寫線程繼續等待。但咱們使用讀寫鎖是由於存在大量的讀線程和很是少的寫線程,這樣寫線程極可能必須長時間地等待,形成寫線程飢餓,不能及時更新數據。更槽糕的狀況是,假如寫線程一直等待,就會形成活鎖。反之,咱們讓 ReaderWriterLock採起寫線程優先的策略。若是存在多個寫線程,而讀線程數量稀少,也會形成讀線程飢餓。幸運的是,現實實踐中,這種狀況不多出現。一旦發生這種狀況,咱們能夠採起互斥鎖的辦法。併發
寫現成飢渴以下圖app
遞歸:ReaderWriterLock類支持鎖遞歸。這就意味着該鎖清楚的知道目前哪一個線程擁有它。假如擁有該鎖的線程遞歸嘗試得到該讀寫鎖,遞歸算法容許該線程得到該讀寫鎖,而且增長得到該鎖的計數。然而該線程必須釋放該鎖相同的次數以便線程再也不擁有該鎖。儘管這看起來是個很好的特性,可是實現這個「特性」代價過高。首先,由於多個讀線程能夠同時擁有該讀寫鎖,這必須讓該鎖爲每一個線程保持計數。此外,還須要額外的內存空間和時間來更新計數。這個特性對 ReaderWriterLock類可憐的性能貢獻極大。其次,有些良好的設計須要一個線程在此處得到該鎖,而後在別處釋放該鎖(好比 .NET 的異步編程架構)。由於這個遞歸特性,ReaderWriterLock 不支持這種編程架構。
資源泄漏:在 .NET 2.0 以前的版本中, ReaderWriterLock 類會形成內核對象泄露。這些對象只有在進程終止後才能再次回收。幸運的是,.NET 2.0 修正了這個 Bug 。
此外:ReaderWriterLock 還有個使人擔憂的危險的非原子性操做。它就是 UpgradeToWriteLock方法。這個方法實際上在更新到寫鎖前先釋放了讀鎖。這就讓其餘線程有機會在此期間乘虛而入,從而得到讀寫鎖且改變狀態。若是先更新到寫鎖,而後釋放讀鎖。假如兩個線程同時更新將會致使另一個線程死鎖。
因此 Microsoft 決定構建一個新類來一次性解決上述全部問題,這就是ReaderWriterLockSlim 類。原本能夠在原有的 ReaderWriterLock 類上修正錯誤,可是考慮到兼容性和已存在的API ,Microsoft 放棄了這種作法。固然也能夠標記 ReaderWriterLock 類爲Obsolete,可是因爲某些緣由,這個類還有存在的必要。
Reader-Reader,第二個不需等待,直接得到讀控制權;
Reader-Writer,第二個須要等待第一個調用釋放讀控制權後,才能得到寫控制權;
Writer-Writer,第二個須要等待第一個調用釋放寫控制權後,才能得到寫控制權;
Writer-Reader,第二個須要等待第一個調用釋放寫控制權後,才能得到讀控制權。
重要事項 |
---|
.NET Framework 具備兩個讀取器 / 編寫器鎖, ReaderWriterLockSlim 和 ReaderWriterLock。 ReaderWriterLockSlim 建議將全部新的開發的。 ReaderWriterLockSlim 相似於 ReaderWriterLock, ,只是簡化了遞歸、 升級和降級鎖定狀態的規則。 ReaderWriterLockSlim 可避免潛在的死鎖的不少狀況。 此外,性能的 ReaderWriterLockSlim 明顯優於 ReaderWriterLock。 |
3、ReaderWriterLockSlim 類
新的ReaderWriterLockSlim 類支持三種鎖定模式:Read,Write,UpgradeableRead。這三種模式對應的方法分別是EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是與此對應的TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 鎖定模式比較簡單易懂:Read 模式是典型的共享鎖定模式,任意數量的線程均可以在該模式下同時得到鎖;Writer模式則是互斥模式,在該模式下只容許一個線程進入該鎖。UpgradeableRead鎖定模式可能對於大多數人來講比較新鮮,可是在數據庫領域卻衆所周知。
這個新的讀寫鎖類性能跟 Monitor 類大體至關,大概在Monitor 類的 2倍以內。並且新鎖優先讓寫線程得到鎖,由於寫操做的頻率遠小於讀操做。一般這會致使更好的可伸縮性。起初,ReaderWriterLockSlim類在設計時考慮到至關多的狀況。好比在早期 CTP 的代碼還提供了PrefersReaders,PrefersWritersAndUpgrades 和 Fifo 等競爭策略。可是這些策略雖然添加起來很是簡單,可是會致使狀況很是的複雜。因此Microsoft 最後決定提供一個可以在大多數狀況下良好工做的簡單模型。
ReaderWriterLockSlim 的更新鎖
如今讓咱們更加深刻的討論一下更新模型。UpgradeableRead 鎖定模式容許安全的從 Read 或 Write 模式下更新。還記得先前ReaderWriterLock的更新是非原子性,危險的操做嗎(尤爲是大多數人根本沒有意識到這點)?如今提供的新讀寫鎖既不會破壞原子性,也不會致使死鎖。新鎖一次只容許一個線程處在 UpgradeableRead 模式下。
一旦該讀寫鎖處在 UpgradeableRead模式下,線程就能讀取某些狀態值來決定是否降級到 Read 模式或升級到 Write 模式。注意應當儘量快的做出這個決定:持有UpgradeableRead 鎖會強制任何新的讀請求等待,儘管已存在的讀取操做仍然活躍。遺憾的是,CLR 團隊移除了DowngradeToRead 和 UpgradeToWrite 兩個方法。若是要降級到讀鎖,只要簡單的在ExitUpgradeableReadLock 方法後緊跟着調用 EnterReadLock 方法便可:這可讓其餘的 Read 和UpgradeableRead 得到完成先前應當持有卻被 UpgradeableRead 鎖持有的操做。若是要升級到寫鎖,只要簡單調用EnterWriteLock 方法便可:這可能要等待,直到再也不有任何線程在 Read 模式下持有鎖。不像降級到讀鎖,必須調用ExitUpgradeableReadLock。在 Write 模式下沒必要非得調用ExitUpgradeableReadLock。可是爲了形式統一,最好仍是調用它。
using System; using System.Linq; using System.Threading; namespace Lucifer.CSharp.Sample { class Program { private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(); void Sample() { bool isUpdated = true; rwLock.EnterUpgradeableReadLock(); try { if (/* … 讀取狀態值來決定是否更新 … */) { rwLock.EnterWriteLock(); try { //… 寫入狀態值 … } finally { rwLock.ExitWriteLock(); } } else { rwLock.EnterReadLock(); rwLock.ExitUpgradeableReadLock(); isUpdated = false; try { //… 讀取狀態值 … } finally { rwLock.ExitReadLock(); } } } finally { if (isUpdated) rwLock.ExitUpgradeableReadLock(); } } } }
ReaderWriterLockSlim 的遞歸策略
新的讀寫鎖還有一個有意思的特性就是它的遞歸策略。默認狀況下,除已說起的降級到讀鎖和升級到寫鎖以外,全部的遞歸請求都不容許。這意味着你不能連續兩次調用 EnterReadLock,其餘模式下也相似。若是你這麼作,CLR 將會拋出 LockRecursionException異常。固然,你可使用 LockRecursionPolicy.SupportsRecursion的構造函數參數讓該讀寫鎖支持遞歸鎖定。但不建議對新的開發使用遞歸,由於遞歸會帶來沒必要要的複雜狀況,從而使你的代碼更容易出現死鎖現象。
有一種特殊的狀況永遠也不被容許,不管你採起什麼樣的遞歸策略。這就是當線程持有讀鎖時請求寫鎖。Microsoft 曾經考慮提供這樣的支持,可是這種狀況太容易致使死鎖。因此 Microsoft 最終放棄了這個方案。
此外,這個新的讀寫鎖還提供了不少對應的屬性來肯定線程是否在指定模型下持有該鎖。好比 IsReadLockHeld,IsWriteLockHeld 和 IsUpgradeableReadLockHeld 。你也能夠經過WaitingReadCount,WaitingWriteCount 和 WaitingUpgradeCount等屬性來查看有多少線程正在等待持有特定模式下的鎖。CurrentReadCount屬性則告知目前有多少併發讀線程。RecursiveReadCount, RecursiveWriteCount 和RecursiveUpgradeCount 則告知目前線程進入特定模式鎖定狀態下的次數。
小結
這篇文章分析了.NET 中提供的兩個讀寫鎖類。然而 .NET 3.5 提供的新讀寫鎖 ReaderWriterLockSlim 類消除了ReaderWriterLock 類存在的主要問題。與 ReaderWriterLock相比,性能有了極大提升。更新具備原子性,也能夠極大避免死鎖。更有清晰的遞歸策略。在任何狀況下,咱們都應該使用ReaderWriterLockSlim 來代替 ReaderWriterLock 類。
Update 於 2008-12-07 0:06
Windows Vista 及其之後的版本新增了一個 SRWLock 原語。它以 Windows 內核事件機制爲基礎而構建。它的設計比較有意思。
SRW 鎖不支持遞歸。Windows Kernel 團隊認爲支持遞歸會形成額外系統開銷,緣由是爲了維持準確性需進行逐線程的計數。SRW鎖也不支持從共享訪問升級到獨佔訪問,同時也不支持從獨佔訪問降級到共享訪問。支持升級能力可能會形成難以接受的複雜性和額外系統開銷,這種開銷甚至會影響鎖內共享和獨佔得到代碼的常見狀況。它還要求定義關於如何選擇等待中的讀取器、等待中的寫入器和等待升級的讀取器的策略,這又將與無偏向的基本設計目標相抵觸。我對其進行了 .NET 封裝。
using System; using System.Threading; using System.Runtime.InteropServices; namespace Lucifer.Threading.Lock { /// <summary> /// Windows NT 6.0 才支持的讀寫鎖。 /// </summary> /// <remarks>請注意,這個類只能在 NT 6.0 及之後的版本中才能使用。</remarks> public sealed class SRWLock { private IntPtr rwLock; /// <summary> /// 該鎖不支持遞歸。 /// </summary> public SRWLock() { InitializeSRWLock(out rwLock); } /// <summary> /// 得到讀鎖。 /// </summary> public void EnterReadLock() { AcquireSRWLockShared(ref rwLock); } /// <summary> /// 得到寫鎖。 /// </summary> public void EnterWriteLock() { AcquireSRWLockExclusive(ref rwLock); } /// <summary> /// 釋放讀鎖。 /// </summary> public void ExitReadLock() { ReleaseSRWLockShared(ref rwLock); } /// <summary> /// 釋放寫鎖。 /// </summary> public void ExitWriteLock() { ReleaseSRWLockExclusive(ref rwLock); } [DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] private static extern void InitializeSRWLock(out IntPtr rwLock); [DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] private static extern void AcquireSRWLockExclusive(ref IntPtr rwLock); [DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] private static extern void AcquireSRWLockShared(ref IntPtr rwLock); [DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] private static extern void ReleaseSRWLockExclusive(ref IntPtr rwLock); [DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] private static extern void ReleaseSRWLockShared(ref IntPtr rwLock); } }
讀寫鎖有個很經常使用的場景就是在緩存設計中。由於緩存中常常有些很穩定,不太長更新的內容。MSDN 的代碼示例就很經典,我原版拷貝一下,呵呵。代碼示例以下:
using System; using System.Threading; namespace Lucifer.CSharp.Sample { public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); public string Read(int key) { cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReadLock(); } } public void Add(int key, string value) { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } } public bool AddWithTimeout(int key, string value, int timeout) { if (cacheLock.TryEnterWriteLock(timeout)) { try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return true; } else { return false; } } public AddOrUpdateStatus AddOrUpdate(int key, string value) { cacheLock.EnterUpgradeableReadLock(); try { string result = null; if (innerCache.TryGetValue(key, out result)) { if (result == value) { return AddOrUpdateStatus.Unchanged; } else { cacheLock.EnterWriteLock(); try { innerCache[key] = value; } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Updated; } } else { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Added; } } finally { cacheLock.ExitUpgradeableReadLock(); } } public void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } public enum AddOrUpdateStatus { Added, Updated, Unchanged }; } }
摘抄轉載方便本身記憶省得處處找
原文:http://blog.csdn.net/zengjibing/archive/2009/02/22/3923168.aspx