When is ReaderWriterLockSlim better than simple locking?

I am doing a very silly ReaderWriterLock test with this code, where reading occurs 4 times more often than writing:

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"; } } 

But I understand that both of them are executed more or less the same:

 Locked: 25003ms. RW Locked: 25002ms. End 

Even making reads 20 times more likely that he writes, performance is still (almost) the same.

Am I doing something wrong here?

Sincerely.

+59
multithreading c # locking
Nov 18 '10 at 16:51
source share
10 answers

In your example, sleep means that, as a rule, there is no argument. Illegal blocking is very fast. For this to matter, you would need a secure castle; if there are records in this conflict, they should be approximately the same ( lock can be even faster), but if it is mostly read (with rare entries), I would expect that the ReaderWriterLockSlim lock will exit lock .

Personally, I prefer a different strategy here using a swapping link - so reading can always read without checking / blocking / etc. Records make their change a cloned copy, and then use Interlocked.CompareExchange to replace the link, applying their change if another thread mutated the link in a period of time).

+76
Nov 18 '10 at 17:02
source share

There are no statements in this program. The Get and Add methods execute in a few nanoseconds. The chances that multiple threads fall into these methods at the exact time are vanishingly small.

Put a call to Thread.Sleep (1) in them and remove the sleep from the threads to see the difference.

+12
Nov 18 '10 at 17:19
source share

My own tests show that ReaderWriterLockSlim has about 5x overhead compared to a regular lock . This means that RWLS is superior to a regular old lock, usually the following conditions arise.

  • The number of readers far exceeds the number of authors.
  • The lock must be kept long enough to overcome the extra overhead.

In most real-world applications, these two conditions are not enough to overcome the extra overhead. In your code, specifically locks are held for such a short period of time that the overhead of locking is likely to be the dominant factor. If you moved these Thread.Sleep calls inside the lock, you would probably get a different result.

+12
Nov 18 '10 at 17:48
source share

Change 2 . Just removing ReadThread calls from ReadThread and WriteThread , I saw Locked outperform RWLocked . I suppose Hans hit a nail on the head; your methods are too fast and do not create competition. When I added Thread.Sleep(1) to the Get and Add Locked and RWLocked (and used 4 read streams versus 1 write stream), RWLocked beat Locked pants.




Edit : Well, if I really thought when I first posted this answer, I would understand at least why you placed Thread.Sleep calls there: you tried to play the read script more often than it writes. This is not the best way to do this. Instead, I would add extra overhead to your Add and Get methods to create a greater chance of contention (as Hans suggested ), create more read threads than write (to provide more frequent reads than writes), and delete Thread.Sleep calls Thread.Sleep from ReadThread and WriteThread (which actually reduce the conflict by reaching the opposite of what you want).




I like what you have done so far. But here are a few questions that I see right off the bat:

  • Why call Thread.Sleep ? This simply accumulates the execution time by a constant amount, which artificially leads to a convergence of work results.
  • I will also not include the creation of new Thread objects in the code measured by your Stopwatch . This is not a trivial object to create.

If you notice a significant difference, if you consider the two questions above, I do not know. But I believe that they should be considered before the discussion continues.

+8
Nov 18 '10 at 17:01
source share

You will get better performance with ReaderWriterLockSlim than simply locking if you block the part of the code that takes longer. In this case, readers can work in parallel. Acquiring ReaderWriterLockSlim takes longer than entering a simple Monitor . Check out my ReaderWriterLockTiny implementation for read / write locks, which is even faster than a simple lock instruction, and offers read / write functions: http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net/

+5
05 Oct '13 at 14:43
source share

Check out this article: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

Your dreams are probably long enough to make your lock / unlock statistically insignificant.

+3
Nov 18 '10 at 17:02
source share

Incomplete locks take microseconds to receive, so your execution time will be overshadowed by your Sleep calls.

+3
Nov 18 '10 at 17:05
source share

Unless you have multi-core hardware (or at least the same as your planned production environment), you won’t get a realistic test here.

A more reasonable test would be to extend the validity of your blocked operations by putting a short delay inside on the lock. That way, you can really compare parallelism with ReaderWriterLockSlim compared to the serialization implied by basic lock() .

Currently, the time that is blocked by your operations is lost in the noise generated by sleep calls that occur outside of the locks. The total time in any case is mainly associated with sleep.

Are you sure that your application in the real world will have an equal number of reads and records? ReaderWriterLockSlim really better for the case when you have many readers and relatively rare authors. 1 writer stream and 3 reads should demonstrate the benefits of ReaderWriterLockSlim better, but in any case, your test should match your expected pattern of access to the real world.

+3
Nov 18 2018-10-18
source share

I suppose this is because of the sleeps that you have, the readings and streams of writers.
Your read thread has a 50-minute sleep, which is 25,000. In most cases, he sleeps.

+2
Nov 18 '10 at 17:00
source share

When is ReaderWriterLockSlim better than simple locking?

If you have significantly more readings than records.

0
Jul 14 '17 at 7:16
source share



All Articles