.NET 同步与异步之锁(ReaderWriterLockSlim)(八)
本随笔续接:.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 : https://files.cnblogs.com/files/08shiyan/ParallelDemo.zip
参见更多:随笔导读:同步与异步
(未完待续...)