Unintelligible

Tuesday, October 20, 2009

Named Reader Writer Lock in C#

A while ago, I found an article about implementing a named lock class in C#, allowing for the following code:

private static NamedLock<string> namedLock = new NamedLock</string><string>();
public void DoSomethingConcurrent(string lockName)
{
    using (namedLock.Lock(lockName))
    {
       //Do something concurrent
    }
}
</string>

This is useful because it allows the same code block to be run concurrently, with locking occurring depending on the arguments being passed in. The author thought it might be useful to have some reader-writer type functionality added in too though; since I had a similar requirement, I wrote a named reader-writer lock, based partly on the NamedLock posted above but using a ReaderWriterLockSlim as the underlying lock rather than a simple Monitor.

I’ve posted the code to Github, including a unit test suite. The most interesting file is the class itself. Usage is almost identical to the NamedLock:

private static NamedReaderWriterLock<string> namedRWLock = new NamedReaderWriterLock</string><string>();
public void DoSomethingConcurrent(string lockName)
{
    using (namedRWLock.LockRead(lockName))
    {
       //Do something concurrent that only requires 
       //read access to the resource
    }
    using (namedRWLock.LockUpgradeableRead(lockName))
    {
       //Do something concurrent that only requires 
       //read access to the resource, but that may require
       //upgrading to a Write lock later in the code
    }
    using (namedRWLock.LockWrite(lockName))
    {
       //Do something concurrent that requires 
       //write access to the resource
    }
}
</string>

Performance-wise, the reader-writer functionality does seem to improve concurrent access over the named lock; here’s the output of the performance tests running on my machine:

NameLock_vs_NamedReaderWriterLockSlim_PerformanceTest_Balanced
 
Run 1 - finished NamedRWLock run in 3734.2794ms
Run 1 - finished NamedLock run in 5499.8592ms
Run 1 - finished Monitor run in 5468.61ms
Run 2 - finished NamedRWLock run in 3609.2826ms
Run 2 - finished NamedLock run in 5468.61ms
Run 2 - finished Monitor run in 5468.61ms
Run 3 - finished NamedRWLock run in 3609.2826ms
Run 3 - finished NamedLock run in 5468.61ms
Run 3 - finished Monitor run in 5515.4838ms
 
3 passed, 0 failed, 0 skipped, took 47.92 seconds.
 
NameLock_vs_NamedReaderWriterLockSlim_PerformanceTest_SkewedRead
 
Run 1 - finished NamedRWLock run in 6656.0796ms
Run 1 - finished NamedLock run in 8843.5236ms
Run 1 - finished Monitor run in 8812.2744ms
Run 2 - finished NamedRWLock run in 6562.332ms
Run 2 - finished NamedLock run in 8812.2744ms
Run 2 - finished Monitor run in 8812.2744ms
Run 3 - finished NamedRWLock run in 6562.332ms
Run 3 - finished NamedLock run in 8812.2744ms
Run 3 - finished Monitor run in 8812.2744ms
 
3 passed, 0 failed, 0 skipped, took 76.59 seconds.

These tests compare the performance of the NamedReaderWriterLock with the NamedLock, using a standard Monitor as a control value. The first set of tests balances reads and writes, whereas the second performs four times more reads than writes (and likely reflects real-life scenarios more closely.)

The NamedReaderWriterLock provided around 35% improvement over the NamedLock in the test where as many writes as reads are performed, and a 25% improvement in the test skewed towards reads. This is a reasonably figure, although in the greater scheme of things it’s likely to be a only a minor improvement compared to the cost of, say, making a DB call; still, it’s worth having.

The code is available under the BSD license.

posted by Nick at 10:17 pm - filed in .net