ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。
在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻止。
注意
长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock 和 DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。
递归锁请求会增加锁上的锁计数。
读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。
当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的阅读器锁持有者共享并发访问,也不会给它们的请求授予权限;这有助于防止编写器被阅读器无限期阻止。
大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。
如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。
1 static void Main(string[] args) 2 { 3 Dictionary<int, string> dic = new Dictionary<int, string>(); 4 ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); 5 Task.Factory.StartNew(() => 6 { 7 int key = 0; 8 while (key <= 1000) 9 { 10 Thread.Sleep(10); 11 string info = $"写线程:{DateTime.Now.ToString("hhMMss-fff")}"; 12 if (lockSlim.TryEnterWriteLock(20)) 13 { 14 try 15 { 16 dic.Add(key++, info); 17 Console.WriteLine(info); 18 } 19 catch (Exception ex) 20 { 21 ; 22 } 23 finally 24 { 25 Console.WriteLine($"释放 {info}"); 26 lockSlim.ExitWriteLock(); 27 } 28 } 29 } 30 }); 31 Task.Factory.StartNew(() => 32 { 33 while (true) 34 { 35 Thread.Sleep(100); 36 int key = 0; 37 if (lockSlim.TryEnterReadLock(20)) 38 { 39 try 40 { 41 while (key < dic.Count) 42 { 43 Console.WriteLine($"读线程1:{dic[key++]}"); 44 } 45 } 46 catch (Exception ex) 47 { 48 ; 49 } 50 finally 51 { 52 lockSlim.ExitReadLock(); 53 } 54 } 55 } 56 }); 57 Task.Factory.StartNew(() => 58 { 59 while (true) 60 { 61 Thread.Sleep(100); 62 int key = 0; 63 if (lockSlim.TryEnterReadLock(20)) 64 { 65 try 66 { 67 while (key < dic.Count) 68 { 69 Console.WriteLine($"读线程2:{dic[key++]}"); 70 } 71 } 72 catch (Exception ex) 73 { 74 ; 75 } 76 finally 77 { 78 lockSlim.ExitReadLock(); 79 } 80 } 81 } 82 }); 83 Task.Factory.StartNew(() => 84 { 85 while (true) 86 { 87 Thread.Sleep(100); 88 int key = 0; 89 if (lockSlim.TryEnterReadLock(20)) 90 { 91 try 92 { 93 while (key < dic.Count) 94 { 95 Console.WriteLine($"读线程3:{dic[key++]}"); 96 } 97 } 98 catch (Exception ex) 99 { 100 ; 101 } 102 finally 103 { 104 lockSlim.ExitReadLock(); 105 } 106 } 107 } 108 }); 109 Task.Factory.StartNew(() => 110 { 111 while (true) 112 { 113 Thread.Sleep(100); 114 int key = 0; 115 if (lockSlim.TryEnterReadLock(20)) 116 { 117 try 118 { 119 while (key < dic.Count) 120 { 121 Console.WriteLine($"读线程4:{dic[key++]}"); 122 } 123 } 124 catch (Exception ex) 125 { 126 ; 127 } 128 finally 129 { 130 lockSlim.ExitReadLock(); 131 } 132 } 133 } 134 }); 135 Console.ReadKey(); 136 }
.NET 同步与异步之锁(ReaderWriterLockSlim)(八)
c#线程同步系列(二) c#中ReaderWriterLock的使用