3

EDIT: From the answers I got already, I understand that the first solution that i presented, not really "Not blocking reads", since only one thread can enter upgradable lock and write lock can not be taken before read is released...

So my question, how to make in correct way the first solution to be "Non blocking read" with creation if not exists?


I'm trying to understand two solutions for non blocking multi threading reads. What is the difference between two solutions below (maybe I still not understand some things, but I'm trying):

/// <summary>
/// ReaderWriterLockSlim pattern
/// </summary>
public class ReadWriteLockCheck
{
    Dictionary<string, object> _dict = new Dictionary<string, object>();

    private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

    public void CreateByKey(string key)
    {
        _rwLock.EnterReadLock();
        try
        {
            if (!_dict.ContainsKey(key)) //Non blocking read - Check if exists
            {
                _rwLock.EnterWriteLock(); //Lock
                try
                {
                    _dict.Add(key, new object());
                }
                finally
                {
                    _rwLock.ExitWriteLock();
                }
            }
        }
        finally
        {
            _rwLock.ExitReadLock();
        }
    }

    public bool GetByKey(string key)
    {
        _rwLock.EnterWriteLock();
        try
        {
            if (_dict.ContainsKey(key)) //Non blocking read
            {
                return true;
            }

            return false;
        }
        finally
        {
            _rwLock.ExitReadLock();
        }
    }
}

/// <summary>
/// Double check lock pattern
/// </summary>
public class MonitorLock
{
    Dictionary<string, object> _dict = new Dictionary<string, object>();

    private object _syncObj = new Object();

    public void CreateByKey(string key)
    {
        if (!_dict.ContainsKey(key)) //Non blocking read - Check if exists
        {
            Monitor.Enter(_syncObj); //Lock
            try
            {
                if (!_dict.ContainsKey(key)) //Check if between first check and lock someone already added
                {
                    _dict.Add(key, new object());
                }
            }
            finally
            {
                Monitor.Exit(_syncObj);
            }
        }
    }

    public bool GetByKey(string key)
    {
        if (_dict.ContainsKey(key)) //Non blocking read
        {
            return true;
        }

        return false;
    }
}

As it looks for me, both of these solutions can make non blocking reads and only blocking when writing...if so, what is a benefit of ReaderWriterLockSlim? As I found in google, Monitor is much faster than ReaderWriterLockSlim. Of course I understand that possible I will get incorrect state of dictionary while reading, but it's OK for me.
Thanks

Alex Dn
  • 5,465
  • 7
  • 41
  • 79
  • 1
    Why not choose an already-invented wheel? `System.Collections.Concurrent.ConcurrentDictionary`? http://msdn.microsoft.com/en-us/library/dd287191.aspx – spender Jan 08 '13 at 13:02
  • There's no benefit in using `Monitor.Enter` / `Monitor.Exit` for that - just use `lock`; in addition to being *simpler*, it handles a few edge cases a bit better (it uses a different overload of `Enter` when it can) – Marc Gravell Jan 08 '13 at 13:10
  • @spender i saw it, but that class available since .NET 4 and we using 3.5 :( – Alex Dn Jan 08 '13 at 13:14
  • @MarcGravell It is very simplified example, I not really can use lock{...} because locking / unlocking done in another manager object. – Alex Dn Jan 08 '13 at 13:16

1 Answers1

5

From MSDN:

Only one thread can enter upgradeable mode at any given time

basically, you haven't done much better than just using a full lock - except lock would actually have been faster.

Oddly enough, one good approach here is Hashtable; especially since the value is object, and the key is a reference-type (no extra boxing). Hashtable is unusual in that reads are fully thread-safe; you only need to guard against multiple writers.

For example:

readonly Hashtable lookup = new Hashtable();

...

object val = lookup[key]; // no need to synchronize when reading

...

lock(lookup)
{
    lookup[key] = newVal; // synchronize when writing
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Ok, I understand...and if I will change the line _rwLock.EnterUpgradeableReadLock(); to _rwLock.EnterReadLock(); does it will make sense? Another point...you say Hashtable can read in thread safe mode....what exactly that means? Does it means that when I use Dictionary and trying to read value, i can get some unexpected problems except of "inconsistent object state"? – Alex Dn Jan 08 '13 at 13:21
  • @AlexDn re the first; if you use `EnterReadLock`, you **must** exit the read-lock before trying to take a write-lock; the two are completely separate. Re the latter: indeed `Dictionary<,>` does **not** make that guarantee, so with a dictionary you must make sure there are no writers even just to do a read. And yes: without that you can get craziness. With a `Hashtable` you **do not** need that; readers just read - no synchronization *at all* is required. Only writers need to synchronize. This simplifies things greatly. – Marc Gravell Jan 08 '13 at 13:24
  • So do I will get some exception when using Dictionary<,> without lock? Or what kind of problems it can do in my case? – Alex Dn Jan 08 '13 at 13:27
  • @AlexDn Yes, a read concurrent with a write in a Dictionary may cause exceptions. – Joachim Isaksson Jan 08 '13 at 18:06
  • MSDN confirms: `Dictionary<,>` [A Dictionary can support multiple readers concurrently, as long as the collection is not modified.](https://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx#Anchor_10) vs `Hashtable` [Hashtable is thread safe for use by multiple reader threads and a single writing thread. It is thread safe for multi-thread use when only one of the threads perform write (update) operations, which allows for lock-free reads provided that the writers are serialized to the Hashtable.](https://msdn.microsoft.com/en-us/library/xfhwa508.aspx#Anchor_10). – Stéphane Gourichon Jan 11 '16 at 15:59