views:

252

answers:

5

Dear ladies and sirs.

I would like to emply the if-lock-if pattern for checking if an object is present in the dictionary in a multithreaded environment. So, the code I am considering looks like so:

private IDictionary<string, SomeType> m_dic = new Dictionary<string, SomeType>();

private SomeType GetSomeObject(string key)
{
  SomeType obj;
  if (!m_dic.TryGetValue(key, out obj))
  {
    lock(m_dic)
    {
      if (!m_dic.TryGetValue(key, out obj))
      {
        m_dic[key] = obj = CreateSomeObject(key);
      }
    }
  }
  return obj;
}

I act on the assumption that even if another thread is inserting the object at the same key right now, the TryGetValue will not return a partially set reference (such thing does not exist in .NET, does it?), rather it will return null and so we enter the protected section and repeat the check.

My question is my assumption correct and the code is right?

Thanks.

EDIT

Let me throw in a restriction. The dictionary is actually a dictionary of singleton objects. So, once an entry is occupied, it is never changed. Just like the Instance property of a singleton - once it is set, it is never changed. Given that constraint, can we use the if-lock-if pattern?

A: 

In .NET it is not possible to get partially set reference. All read/write reference operations are atomic.

Code looks fine :) But don't forget to add synchronization to the other operations, like INSERT and UPDATE.

Vitaliy Liptchinsky
+4  A: 

Edited for clarity:

No this is a very bad idea. You can play an if-lock game on something simple and atomic (an int) but a Dictionary is a class with multiple moving parts. Reading and Writing must be synchronized at all times, See the ThreadSafety section on this MSDN page.

Henk Holterman
I know that. But I want to know whether its internal state is going to be corrupted if one thread is asking (TryGetValue) while another thread is writing. Or the returned answer is simply going to be null when in reality there is a valid object already inserted by another thread. If the state is corrupted - then the code is wrong. If, however, TryGetValue simply returned null, then the code is right, because it repeats the call inside a protected section. Please, elaborate your reply.
mark
I have edited my question to clear things a bit. MSDN article talks about general cases, my case is special.
mark
And what if the order is different? Suppose it is Add Key first, then TryGetValue returns null, because there is no Add Value yet, which brings us to the protected section with the lock.
mark
If TryGetValue is not documented as thread-safe, then the return value in a multi-threaded environment is just not defined. It can be anything. Of course, you could look into the source code of TryGetValue and discover that -- coincidentally -- it returns null in the case you are describing. Even in that case, you should not rely on it (see "Design by contract"), since the precise source code might change in future versions of the Framework.
Heinzi
When `Add()` adds the Key first then `TryGetvalue` could throw an OutOfRange Exception. Lots of other things can go wrong as well.
Henk Holterman
_All_ Disctionary methods are not tread-safe. So basically, there are no guaranties at all, and provided answer is fully correct. This is a _very bad_ practice.
Alex Yakunin
On the other hand, there is Hashtable type, which, although being generally not thread safe, provides thread safety for one frequent scenario: many readers, single writer. See http://tips.x-tensive.com/2008/10/when-hashtable-is-better-then.html
Alex Yakunin
So how this is related to this problem? Very simple: such a locking scheme _ensures_ there are _many readers, but just a single writer_. So if you replace Dictionary to Hashtable here, this approach will work as expected.
Alex Yakunin
+2  A: 

From http://www.yoda.arachsys.com/csharp/singleton.html:

Locking on objects which other classes can access and lock on (such as the type) risks performance issues and even deadlocks. This is a general style preference of mine - wherever possible, only lock on objects specifically created for the purpose of locking, or which document that they are to be locked on for specific purposes (e.g. for waiting/pulsing a queue). Usually such objects should be private to the class they are used in. This helps to make writing thread-safe applications significantly easier.

Furthermore, double-checked locking is almost always a bad idea. Hence, I would use code similar to the following:

private static readonly object m_padlock = new object();
private IDictionary<string, SomeType> m_dic = new Dictionary<string, SomeType>();

private SomeType GetSomeObject(string key)
{
  SomeType obj;

  lock (m_padlock)
  {
    if (!m_dic.TryGetValue(key, out obj))
    {
      m_dic[key] = obj = CreateSomeObject(key);
    }
  }

  return obj;
}
Ian Kemp
But m_dic is private so the argument for m_padlock is weak. I do agree with the second part.
Henk Holterman
Having the dedicated lock object is not an issue. My question concentrates on the if-lock-if pattern applicability to dictionaries.
mark
I have edited my question to clear things a bit.
mark
@Henk: The developer of "Dictionary<,>" might have used lock(this) somewhere.
Heinzi
Heinzi, I would expect _that_ to be documented. And avoided by the BCL team. But I suppose that in general you are right.
Henk Holterman
A: 

I think ReaderWriterLock is better than approach in this senario. furthermore, you have multiple readers, but only one writer.

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

Mehdi Golchin
The point is try and avoid the penalty of lock when the object is already present in the dictionary.
mark
`ReaderWriterLockSlim` has replaced `ReaderWriterLock`.
280Z28
readerwriterlock isn't that good at all either: http://msdn.microsoft.com/en-us/magazine/cc163599.aspx
SnOrfus
+2  A: 

See my comments to the correct reply. Important part is: if you replace Dictionary to Hashtable in your example, this approach will work.

Alex Yakunin