Is the double check lock pattern necessary when using ReaderWriterLockSlim?
Consider this scenario: I have a dictionary. Things can be added to it. But things can’t be removed from it. When things are added, it can be a very expensive operation in terms of time (only hundreds of milliseconds, but still costly relative to the rest of the app) If I wanted to add something and it wasn’t already there, would there be anything to gain by:
- first acquiring a read lock, then checking for existence,
- then entering an upgradable read lock, and checking again,
- then entering a write lock if the item is still not present in the dictionary?
Something like the following:
void populateIfNotPresent( object thing )
{
_lock.EnterReadLock( ) ;
bool there = _dictionary.ContainsKey(thing);
_lock.ExitReadLock( ) ;
// Remember, the specs say nothing can be removed from this dictionary.
if (!there)
{
_lock.EnterUpgradeableReadLock( ) ;
try
{
if( !_dictionary.ContainsKey( thing ) )
{
_lock.EnterWriteLock( ) ;
try
{
populate( thing ) ;
}
finally
{
_lock.ExitWriteLock( ) ;
}
}
}
finally
{
_lock.ExitUpgradeableReadLock( ) ;
}
}
}
The docs say only one thread at a time can enter an upgradable read lock, but doesn’t stop any other threads from entering a read lock, so it appears that there is value in the double check lock.
What do you think? Is this overkill?
The
ReaderWriterLockSlimclass (like any other reader-writer lock) is meant for a high number of reads compared to the number of writes.What you are doing is actually triple-checking, and it’s superfluous; you might as well enter an ungradable write lock. If the item exists, then exit the lock, otherwise, upgrade to a write lock.
Your method indicates that the read is not providing any value here, since you have a good possibility of performing the write. Since the upgradable write won’t block any other reads, it shouldn’t kill you here.
However, if this is the only place that you are doing reads/writes (or the majority take place here) then there’s a problem, your ratio of reads/writes isn’t high enough to warrant a read/write lock and you should look to some other synchronization method.
That said, in the end, it’s all about testing the performance; if you’re going to optimize an implementation, you need to measure it’s current performance in order to make a comparison against, otherwise, it’s just premature optimization.