IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

ReaderWriterLock vs Monitor

Monday 26 April, 2004, 05:58 PM

Sebastien Lambla posted an article about why he prefers the ReaderWriterLock over the good old Monitor that I blogged about last month. As he says:

"Well most of the time what you really want is let several threads read, but only one thread only write. Oh, and you don't want to let someone write while you're reading, nor do you want anyone reading while you write."

This all sounds perfectly reasonable, but I don't think it's as simple as that.

The right choice of synchronization primitive will be determined not only by the pattern of access to your data, but also the amount of contention. Even if your access patterns are as Sebastian describes - what I like to call "almost read-only" access - you still might find that ReaderWriterLock performs less well than Monitor.

This seems counterintuitive. The Monitor forces threads to take it in turns, while the ReaderWriterLock only serializes access when writes occur. You could be forgiven for thinking that the one that allows the most work to proceed in parallel will give you the best throughput.

In practice, you're only going to see better throughput if you really do have lots of threads trying to use the resource simultaneously - ReaderWriterLock only offers any benefit if there is a significant amount of contention for the resources you are protecting. That won't always be the case - consider something like this:

private static ArrayList fooList;
public Foo CreateFoo()
{
    Foo f = new Foo();
    lock (fooList)
    {
        fooList.Add(f);
    }
    return f;
}

How long do you suppose we're going to spend holding the monitor in this code? Probably not all that long - you would expect ArrayList.Add to run pretty quickly. To answer the question fully, we'd need to know things like how often CreateFoo is called, and how long it takes to construct a Foo, but unless the answers are 'lots' and 'very little', the chances are that the vast majority of the time will be spent outside of that lock block. And this is not a particularly unusual example.

Assuming that all the uses of this particular object's monitor are equally short-lived, how often will two or more threads actually be in contention for it?

On a single-CPU system, it wouldn't be surprising if there was never any contention for the lock. Code that doesn't perform any blocking operations while holding the monitor (like the example above) will only encounter contention if the thread's quantum happens to expire while the lock is held, and the OS scheduler pre-empts it and then runs another thread which then tries to acquire the same object's monitor. This is going to be an extremely rare occurrence. (This is one reason why threading bugs tend to manifest themselves far less often on single-CPU systems than on multi-CPU machines.)

Even on a multi-CPU box, it is quite plausible that contention for this monitor will be the exception rather than the rule. It will depend on the pattern of access to the data, but unless the shared data structure is a hot spot being used by many threads most of the time, you may well find that the lock is acquired without contention the vast majority of the time. Indeed, that's a desirable goal for your locking strategies - lock contention is usually bad for performance.

So if a particular monitor is acquired without contention the vast majority of the time, what will changing over to a ReaderWriterLock achieve? As Sebastien points out, it makes your code more complex. It is also likely to slow you down - the cost of acquiring a read lock on a ReaderWriterLock without contention is much higher than the cost of acquiring a monitor without contention. In my tests the monitor looks to be over 5 times faster. So in cases where there is very rarely contention for the lock, the monitor is likely to work out much faster as well as simpler.

Also, remember that most of the classes in the .NET Framework aren't written to be safe even for read-only use - most of them are documented as being safe only for access on one thread at a time. In these cases, the ReaderWriterLock is of no use to you. Of course the DataSet and DataTable are notable exceptions - both of these say the following under Thread Safety:

This type is safe for multithreaded read operations. You must synchronize any write operations

So you'd think these would be a case where ReaderWriterLock could be used safely. (Maybe even to good effect, if you're doing substantial amounts of work while holding the lock.) But even then you have to be careful - it's not entirely clear what constitutes a 'read'. For example, consider DataTable.Select - this retrieves a filtered, sorted subset of what is in the table. It does not alter the data in the table, nor have any directly observable side effects on the DataTable's internal state. And yet Microsoft considers it to be a write operation!

I only know that DataTable.Select counts as a 'write' operation because I wrote some code that assumed it was a read operation, and it fell over under heavy load on a multi-CPU machine - Microsoft informed me of the method's non-obvious and undocumented categorisation as a 'write' method in the ensuing support incident. The reason is that DataTable updates its internal index cache when you call this method. Even though that's an implementation detail, Microsoft still regards this as a write operation... (I'm not sure how we are supposed to know that from the docs, but that's how it is.)

So, what's the best thing to do?

I believe that choosing ReaderWriterLock as your preferred default is at best premature optimisation, and in practice is likely to cause harm more often than it delivers any benefit. It complicates the code, and it might slow you down. You should only consider using ReaderWriterLock when you are seeing lots of contention. (You can use the good old Windows Performance Monitor to see how much lock contention you're getting. Working out exactly which lock(s) are suffering most is more of a challenge, but this essay on deadlock and contention debugging has a lot of useful tips.) And even then you should measure your performance with both styles of locking, using as realistic a workload as you can get, to find out whether the benefits you are hoping for are delivered in practice.

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk