本隨筆續接:.NET 同步與異步之鎖(Lock、Monitor)(七)
由於鎖 ( lock 和 Monitor ) 是線程獨占式訪問的,所以其對性能的影響還是蠻大的,那有沒有一種方式可是實現:允許多個線程同時讀數據、只允許一個線程寫數據呢?答案是肯定的。
讀寫鎖 ReaderWriterLock 、就是 支持單個寫線程和多個讀線程的鎖。自.NET 3.5 開始 ReaderWriterLockSlim 、登上舞台,ReaderWriterLockSlim 可以看做是 ReaderWriterLock 的升級版。 由於 ReaderWriterLockSlim 默認不支持遞歸調用、所以在某種意義上來說更不容易造成死鎖。
一、先看一下demo(來源msdn代碼示例):

public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); public int Count { get { return innerCache.Count; } } 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 }; ~SynchronizedCache() { if (cacheLock != null) cacheLock.Dispose(); } } private void ReaderWriterLock() { var sc = new SynchronizedCache(); var tasks = new List<Task>(); int itemsWritten = 0; // Execute a writer. tasks.Add(Task.Run(() => { String[] vegetables = { "broccoli", "cauliflower", "carrot", "sorrel", "baby turnip", "beet", "brussel sprout", "cabbage", "plantain", "spinach", "grape leaves", "lime leaves", "corn", "radish", "cucumber", "raddichio", "lima beans" }; for (int ctr = 1; ctr <= vegetables.Length; ctr++) sc.Add(ctr, vegetables[ctr - 1]); itemsWritten = vegetables.Length; base.PrintInfo(string.Format("Task {0} wrote {1} items\n", Task.CurrentId, itemsWritten)); })); // Execute two readers, one to read from first to last and the second from last to first. for (int ctr = 0; ctr <= 1; ctr++) { bool desc = Convert.ToBoolean(ctr); tasks.Add(Task.Run(() => { int start, last, step; int items; do { String output = String.Empty; items = sc.Count; if (!desc) { start = 1; step = 1; last = items; } else { start = items; step = -1; last = 1; } for (int index = start; desc ? index >= last : index <= last; index += step) output += String.Format("[{0}] ", sc.Read(index)); base.PrintInfo(string.Format("Task {0} read {1} items: {2}\n", Task.CurrentId, items, output)); } while (items < itemsWritten | itemsWritten == 0); })); } // Execute a red/update task. tasks.Add(Task.Run(() => { Thread.Sleep(100); for (int ctr = 1; ctr <= sc.Count; ctr++) { String value = sc.Read(ctr); if (value == "cucumber") if (sc.AddOrUpdate(ctr, "green bean") != SynchronizedCache.AddOrUpdateStatus.Unchanged) base.PrintInfo("Changed 'cucumber' to 'green bean'"); } })); // Wait for all three tasks to complete. Task.WaitAll(tasks.ToArray()); // Display the final contents of the cache. base.PrintInfo(""); base.PrintInfo("Values in synchronized cache: "); for (int ctr = 1; ctr <= sc.Count; ctr++) base.PrintInfo(string.Format(" {0}: {1}", ctr, sc.Read(ctr))); }
二、通過Demo我們來看一下 ReaderWriterLockSlim 的用法:
1、EnterWriteLock 進入寫模式鎖定狀態
2、EnterReadLock 進入讀模式鎖定狀態
3、EnterUpgradeableReadLock 進入可升級的讀模式鎖定狀態
並且三種鎖定模式都有超時機制、對應 Try... 方法,退出相應的模式則使用 Exit... 方法,而且所有的方法都必須是成對出現的。
三、備注及注意事項
1、對於同一把鎖、多個線程可同時進入 讀模式。
2、對於同一把鎖、同時只允許一個線程進入 寫模式。
3、對於同一把鎖、同時只允許一個線程進入 可升級的讀模式。
4、通過默認構造函數創建的讀寫鎖是不支持遞歸的,若想支持遞歸 可通過構造 ReaderWriterLockSlim(LockRecursionPolicy) 創建實例。
5、對於同一把鎖、同一線程不可兩次進入同一鎖狀態(開啟遞歸后可以)
6、對於同一把鎖、即便開啟了遞歸、也不可以在進入讀模式后再次進入寫模式或者可升級的讀模式(在這之前必須退出讀模式)。
7、再次強調、不建議啟用遞歸。
8、讀寫鎖具有線程關聯性,即 兩個線程間擁有的鎖的狀態 相互獨立不受影響、並且不能相互修改其鎖的狀態。
9、升級狀態:在進入可升級的讀模式 EnterUpgradeableReadLock 后,可在恰當時間點 通過 EnterWriteLock 進入寫模式。
10、降級狀態:可升級的讀模式可以降級為讀模式:即 在進入可升級的讀模式 EnterUpgradeableReadLock 后, 通過首先調用讀取模式 EnterReadLock 方法,然后再調用 ExitUpgradeableReadLock 方法。
隨筆暫告一段落、下一篇隨筆介紹:輕量級的鎖(Interlocked、SpinLock)(預計1篇隨筆)
附,Demo : http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip
參見更多:隨筆導讀:同步與異步
(未完待續...)