一、簡介html
.NET 3.5 開始 ReaderWriterLockSlim登上舞臺,ReaderWriterLockSlim 能夠看作是 ReaderWriterLock 的升級版。 因爲 ReaderWriterLockSlim 默認不支持遞歸調用、因此在某種意義上來講更不容易形成死鎖。
ReaderWriterLockSlim 類支持三種鎖定模式:Read,Write,UpgradeableRead。這三種模式對應的方法分別是 EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是與此對應的 TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 鎖定模式比較簡單易懂:Read 模式是典型的共享鎖定模式,任意數量的線程均可以在該模式下同時得到鎖;Writer 模式則是互斥模式,在該模式下只容許一個線程進入該鎖。UpgradeableRead 鎖定模式可能對於大多數人來講比較新鮮,可是在數據庫領域卻衆所周知。數據庫
一、對於同一把鎖、多個線程可同時進入讀模式。
二、對於同一把鎖、同時只容許一個線程進入寫模式。
三、對於同一把鎖、同時只容許一個線程進入可升級的讀模式。
四、經過默認構造函數建立的讀寫鎖是不支持遞歸的,若想支持遞歸 可經過構造 ReaderWriterLockSlim(LockRecursionPolicy) 建立實例。
五、對於同一把鎖、同一線程不可兩次進入同一鎖狀態(開啓遞歸後能夠)
六、對於同一把鎖、即使開啓了遞歸、也不能夠在進入讀模式後再次進入寫模式或者可升級的讀模式(在這以前必須退出讀模式)。
七、再次強調、不建議啓用遞歸。
八、讀寫鎖具備線程關聯性,即兩個線程間擁有的鎖的狀態相互獨立不受影響、而且不能相互修改其鎖的狀態。
九、升級狀態:在進入可升級的讀模式 EnterUpgradeableReadLock後,可在恰當時間點經過EnterWriteLock進入寫模式。
十、降級狀態:可升級的讀模式能夠降級爲讀模式:即在進入可升級的讀模式EnterUpgradeableReadLock後, 經過首先調用讀取模式EnterReadLock方法,而後再調用 ExitUpgradeableReadLock 方法。api
這段簡介來自https://www.cnblogs.com/majiang/p/8133979.html,來自一個前輩的文章,總結的很好,並且有源碼解析,有興趣的能夠觀看,經過這段話結合MSDN關於ReaderWriterLockSlim的介紹,能大體得知道ReaderWriterLockSlim得用處,在多線程併發操做共享資源時,頗有用處.緩存
二、經過ReaderWriterLockSlim封裝一個同步緩存實例安全
下面時MS提供的封裝,我作了略微的修改,添加了一些註釋,使API更能看懂,代碼以下:多線程
public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); /// <summary> /// 同步緩存塊維護的數據資源 /// </summary> private Dictionary<int, string> innerCache = new Dictionary<int, string>(); /// <summary> /// 同步緩存塊維護的數據資源長度 /// </summary> public int Count { get { return innerCache.Count; } } /// <summary> /// 線程安全的添加操做 /// </summary> /// <param name="key"></param> /// <param name="value"></param> public void Add(int key,string value) { //嘗試進入寫入模式鎖定狀態 cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { //退出寫入模式鎖定狀態 cacheLock.ExitWriteLock(); } } /// <summary> /// 帶鎖超時的添加的操做 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="timeout"></param> /// <returns></returns> 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; } } /// <summary> /// 線程安全的讀取操做 /// </summary> /// <param name="key"></param> /// <returns></returns> public string Read(int key) { cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReadLock(); } } /// <summary> /// 線程安全的添加修改操做 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> 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(); } } /// <summary> /// 線程安全的刪除操做 /// </summary> /// <param name="key"></param> public void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } /// <summary> /// 添加或修改時產生的狀態 /// </summary> public enum AddOrUpdateStatus { Added, Updated, Unchanged }; /// <summary> /// 析構 釋放資源 /// </summary> ~SynchronizedCache() { if (cacheLock != null) cacheLock.Dispose(); } }
下面時使用案列代碼以下:併發
var lockCache = new SynchronizedCache(); var tasks = new List<Task>();//模擬線程集合 //注入寫入內容線程 tasks.Add(Task.Run(() => { var list = new List<string> {"鍾","聲","響","起","歸","家","的","訊","號"}; var listCount = list.Count; for (var i = 0; i < listCount; i++) { lockCache.Add(i, list[i]); } Console.WriteLine($"Task {Task.CurrentId} wrote {listCount} items\n"); })); //注入兩個讀線程,一個正向遍歷同步緩存塊維護的數據資源一個逆向遍歷同步緩存塊維護的數據資源 //因爲讀線程可能在寫線程以前執行,因此輸入內容時可能爲空 for (var i = 0; i <= 1; i++) { var flag = Convert.ToBoolean(i); tasks.Add(Task.Run(() => { int startIndex, lastIndex, step;//開始、結束索引、遞增指數 string outPut=string.Empty;//輸出 int items;//線程執行順序可能不一樣,因此個參數用於判斷在執行讀取操做時,上面的寫入線程是否執行完畢 do { items = lockCache.Count; //正向遍歷 if (!flag) { startIndex = 0; lastIndex = items; step = 1; } //反向遍歷 else { startIndex = items - 1; lastIndex = 0; step = -1; } for (var j = startIndex; flag ? j >= lastIndex : j < lastIndex; j += step) { outPut += $"{lockCache.Read(j)} "; } Console.WriteLine($"Task {Task.CurrentId} read {items} items: {outPut}\n"); } while (lockCache.Count == 0 | items< lockCache.Count); })); } //注入一個線程去修改數據 tasks.Add(Task.Run(() => { Thread.Sleep(100);//強制當前線程休息,防止寫入數據線程尚未執行完畢,就去更新了數據 for (int ctr =0; ctr < lockCache.Count; ctr++) { string value = lockCache.Read(ctr); if (value == "家") if (lockCache.AddOrUpdate(ctr, "Home") != SynchronizedCache.AddOrUpdateStatus.Unchanged) Console.WriteLine("Changed '家' to 'Home'"); } })); Task.WhenAll(tasks).ContinueWith(task => { Console.WriteLine(); Console.WriteLine("Values in synchronized cache: "); for (int ctr = 0; ctr < lockCache.Count; ctr++) Console.WriteLine(" {0}: {1}", ctr, lockCache.Read(ctr)); }); Console.ReadKey();
調用完畢,有點ConncurrentDictionary的味道,還沒看它的代碼,接下去的隨筆會分析,對比下兩種方式的差距.函數