ReaderWriterLockSlim是什么时候比一个简单的锁好?
我使用这段代码在ReaderWriterLock上做了一个非常愚蠢的testing,在这种情况下,阅读比写作经常发生4倍:
class Program { static void Main() { ISynchro[] test = { new Locked(), new RWLocked() }; Stopwatch sw = new Stopwatch(); foreach ( var isynchro in test ) { sw.Reset(); sw.Start(); Thread w1 = new Thread( new ParameterizedThreadStart( WriteThread ) ); w1.Start( isynchro ); Thread w2 = new Thread( new ParameterizedThreadStart( WriteThread ) ); w2.Start( isynchro ); Thread r1 = new Thread( new ParameterizedThreadStart( ReadThread ) ); r1.Start( isynchro ); Thread r2 = new Thread( new ParameterizedThreadStart( ReadThread ) ); r2.Start( isynchro ); w1.Join(); w2.Join(); r1.Join(); r2.Join(); sw.Stop(); Console.WriteLine( isynchro.ToString() + ": " + sw.ElapsedMilliseconds.ToString() + "ms." ); } Console.WriteLine( "End" ); Console.ReadKey( true ); } static void ReadThread(Object o) { ISynchro synchro = (ISynchro)o; for ( int i = 0; i < 500; i++ ) { Int32? value = synchro.Get( i ); Thread.Sleep( 50 ); } } static void WriteThread( Object o ) { ISynchro synchro = (ISynchro)o; for ( int i = 0; i < 125; i++ ) { synchro.Add( i ); Thread.Sleep( 200 ); } } } interface ISynchro { void Add( Int32 value ); Int32? Get( Int32 index ); } class Locked:List<Int32>, ISynchro { readonly Object locker = new object(); #region ISynchro Members public new void Add( int value ) { lock ( locker ) base.Add( value ); } public int? Get( int index ) { lock ( locker ) { if ( this.Count <= index ) return null; return this[ index ]; } } #endregion public override string ToString() { return "Locked"; } } class RWLocked : List<Int32>, ISynchro { ReaderWriterLockSlim locker = new ReaderWriterLockSlim(); #region ISynchro Members public new void Add( int value ) { try { locker.EnterWriteLock(); base.Add( value ); } finally { locker.ExitWriteLock(); } } public int? Get( int index ) { try { locker.EnterReadLock(); if ( this.Count <= index ) return null; return this[ index ]; } finally { locker.ExitReadLock(); } } #endregion public override string ToString() { return "RW Locked"; } }
但是我知道这两个performance方式都差不多:
Locked: 25003ms. RW Locked: 25002ms. End
即使使读取频率高出20倍,性能仍然(几乎)相同。
我在这里做错了什么?
亲切的问候。
在你的例子中,睡眠意味着通常没有争用。 一个无争议的锁是非常快的。 对于这个问题,你需要一个竞争的锁; 如果在这个争用中有写入 ,它们应该是大致相同的( lock
甚至可能会更快),但是如果它主要是读取(很less写入争用),我期望ReaderWriterLockSlim
locking将超出lock
。
就我个人而言,我更喜欢使用引用交换的另一种策略 – 所以读取总是可以不经过检查/locking/等读取。写入将其更改为克隆副本,然后使用Interlocked.CompareExchange
交换引用(重新应用其更改如果另一个线程在过渡期间突变参考)。
这个scheme没有争议。 Get和Add方法在几个纳秒内执行。 多个线程在确切的时间点击这些方法的几率是微乎其微的。
把一个Thread.Sleep(1)调用,并从线程中删除睡眠,看看有什么不同。
我自己的testing表明,与普通的lock
相比, ReaderWriterLockSlim
大约有5倍的开销。 这意味着对于RWLSperformance优于普通的旧锁,通常会发生以下情况。
- 读者人数大大超过作家。
- 锁必须足够长以克服额外的开销。
在大多数实际应用中,这两个条件不足以克服额外的开销。 在你的代码中,locking的时间很短,locking开销可能是主要的因素。 如果您要将这些Thread.Sleep
调用移入锁中,那么您可能会得到不同的结果。
编辑2 :只需从ReadThread
和WriteThread
删除Thread.Sleep
调用,我看到Locked
优于RWLocked
。 我相信汉斯在这里碰到了头, 你的方法太快,没有争用。 当我将Thread.Sleep(1)
Add
到Locked
和RWLocked
的Get
和Add
方法(并且针对1个写入线程使用了4个读取线程)时, RWLocked
击败了Locked
的裤子。
编辑 :好的,如果我真的想到当我第一次发布这个答案的时候,我会意识到至less为什么你把Thread.Sleep
调用放在那里:你试图重现比写更频繁的读操作场景。 这只是不正确的做法。 相反,我会给你的Add
和Get
方法引入额外的开销,以创造更大的争用机会(如Hans所build议的 ),创build比写入线程更多的读取线程(以确保比写入更频繁的读取),并删除Thread.Sleep
调用从ReadThread
和WriteThread
(实际上减less争用,达到你想要的相反)。
我喜欢你迄今为止所做的。 但是这里有几个问题我马上看到:
- 为什么
Thread.Sleep
调用? 这些只是使您的执行时间膨胀恒定,这将人为地使性能结果收敛。 - 我也不会在您的
Stopwatch
测量的代码中包含新的Thread
对象的创build。 这不是一个微不足道的东西。
一旦你解决了上述两个问题,你是否会看到显着的差异,我不知道。 但是我认为在讨论继续之前应该解决。
如果您locking需要较长时间执行的代码的一部分,您可以使用ReaderWriterLockSlim
获得更好的性能。 在这种情况下,读者可以并行工作。 获取ReaderWriterLockSlim
需要比input一个简单的Monitor
更多的时间。 请检查我的ReaderWriterLockTiny
实现的读写器locking,它比简单的locking语句更快,并提供读写器function: http : //i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net/
看看这篇文章: http : //blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx
你的睡眠可能足够长,他们使你的locking/解锁统计微不足道。
除非你有多核硬件(或者至less和你计划的生产环境相同),否则你不会在这里得到真实的testing。
一个更明智的testing就是延长locking操作的寿命。 这样,你应该真正能够对比使用ReaderWriterLockSlim
添加的并行性和基本的lock()
所隐含的序列化。
目前,被locking的操作所花费的时间会在锁之外发生的睡眠呼叫所产生的噪音中丢失。 这两种情况下的总时间大多与睡眠有关。
你确定你的真实世界的应用程序将有相同数量的读取和写入? ReaderWriterLockSlim
对于你有很多读者和相对稀less的作者的情况来说确实更好。 1个线程与3个线程读者应该更好地展示ReaderWriterLockSlim
好处,但是无论如何,你的testing应该符合你期望的真实世界访问模式。
我想这是因为你在读者和作家线程中的睡眠。
你的阅读线程有500人50ms的睡眠时间,大概是 25000
未经validation的锁占用了微秒级的时间,所以你的执行时间将会被你对Sleep
的调用所Sleep
。
ReaderWriterLockSlim是什么时候比一个简单的锁好?
当你有更多的读取比写入。