11

On working with thread-safety, I find myself always "double checking" before executing code in a lock block and I wondered if I was doing the right thing. Consider the following three ways of doing the same thing:

Example 1:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    if(MyCollection[key] == null)
    {
         lock(locker)
         {
              MyCollection[key] = DoSomethingExpensive(); 
         }
    }
    DoSomethingWithResult(MyCollection[key]);
}

Example 2:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    lock(locker)
    {
         if(MyCollection[key] == null)
         {
              MyCollection[key] = DoSomethingExpensive(); 
         }
    }
    DoSomethingWithResult(MyCollection[key]);
}

Example 3:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    if(MyCollection[key] == null)
    {
        lock(locker)
        {
             if(MyCollection[key] == null)
             {
                  MyCollection[key] = DoSomethingExpensive(); 
             }
        }
    }
    DoSomethingWithResult(MyCollection[key]);
}

I always lean towards Example 3, and here's why I think I'm doing the right thing

  • Thread 1 enters DoSomething(string)
  • MyCollection[key] == null so Thread 1 obtains a lock, just as Thread 2 enters
  • MyCollection[key] == null is still true, so Thread 2 waits to obtain the lock
  • Thread 1 calculates the value for MyCollection[key] and adds it to the collection
  • Thread 1 releases the lock and calls DoSomethingWithResult(MyCollection[key]);
  • Thread 2 obtains the lock, by which time MyCollection[key] != null
  • Thread 2 does nothing, releases the lock and continues on its merry way

Example 1 would work, but there is a big risk that Thread 2 could redundantly calculate MyCollection[key].

Example 2 would work, but every thread would obtain a lock, even if it didn't need to - which could be a (admittedly very small) bottleneck. Why hold up threads if you don't need to?

Am I overthinking this and if so, what is the preferred way of handling these situations?

Iain Fraser
  • 6,578
  • 8
  • 43
  • 68
  • 5
    Yes, it's a [common pattern](http://en.wikipedia.org/wiki/Double-checked_locking) – zerkms Nov 06 '12 at 23:57
  • Wow, and that's even what it's called - I thought I was being simple-minded by using the term "double-check" and figured there'd be no way - if it were a pattern - that that's what it would be called. Should I delete the question or leave it up for the Googlez? – Iain Fraser Nov 06 '12 at 23:59
  • 3
    As used in the code above though, I think it makes no sense. If the collection is thread safe, it isn't really double-checked locking since the check will involve acquiring a lock. If the collection isn't thread safe, the first check could cause a crash -- consider if you try to read from the collection while another thread is writing to it. – David Schwartz Nov 07 '12 at 00:07
  • It's been voted up, so I'd leave it for the mods to decide if it should be removed. – TheEvilPenguin Nov 07 '12 at 00:07
  • @DavidSchwartz; to un-abstract the code for you, here I am actually working with `System.Web.Caching.Cache` - which is Thread Safe, so no need for this pattern. And good catch with Example 1, I didn't think of that when I came up with it. If you want to flesh this out with an answer, I'd be happy to accept it - it added to my knowledge and got specific about where this pattern is not required. – Iain Fraser Nov 07 '12 at 00:13
  • 1
    @IainFraser wait ... just because `Cache` is thread safe doesn't mean you shouldn't use the pattern here. Isn't the point to prevent multiple threads from concurrently starting `DoSomethingExpensive`? – McGarnagle Nov 07 '12 at 00:20
  • @dbaseman and @DavidSchwartz; yes, I think I misunderstood what David was saying when I first read it. But if what he says is true, then there's no way you can safely "check" outside the lock on a non-thread-safe collection - is there and alternative in this eventuality? Also, from what I read in the wiki link, it really depends on the language/hardware combo as to whether or not this is safe to do in the first place. I'm guessing this is safe in C#? – Iain Fraser Nov 07 '12 at 00:24
  • More importantly... regardless of whether the collection is thread safe or not, you can have two threads both see that `MyCollection[key]` is `null` safely and have both threads call `DoSomethingExpensive`, possibly wasting CPU cycles, and then they will *both* store the result in `MyCollection[key]`, one overwriting the last. The lock is necessary to avoid this case unless `DoSomethingExpensive` is a *function* in the mathematical sense. – D.Shawley Nov 07 '12 at 00:25
  • 1
    Some links describing why this pattern can be harmful: http://msdn.microsoft.com/en-us/library/ff650316.aspx, http://haacked.com/archive/2007/03/19/double-check-locking-and-other-premature-optimizations-can-shoot-you.aspx, http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html, http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx. This describes how the .NET 2.0 memory model was changed to support the pattern, but I don't think it extends to collections http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10. – fsimonazzi Nov 07 '12 at 00:43

4 Answers4

7

The first method should not be used. As you realised, it leaks, so more than one thread can end up running the expensive method. The longer that method takes, the bigger is the risk that another thread will also run it. In most cases it's only a performance problem, but in some cases it might also be a problem that the resulting data is later on replaced by a new set of data.

The second method is the most common way, the third method is used if the data is accessed so frequently that the locking becomes a performance issue.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    +1. Note on 3rd method - as it written it assumes that `collection[Key]` is thread safe - I don't believe most regular collections provide such guarantee. I.e. internally it can do some iteration of inner structures (think Dictionary) which may be changed by other threads at the same time. – Alexei Levenkov Nov 07 '12 at 00:23
  • @AlexeiLevenkov: Yes, that is a good point, the third method only works if the check in itself is thread safe. – Guffa Nov 07 '12 at 00:30
  • @AlexeiLevenkov; in this case my collection just so happens to be thread safe, so it works. But what would you do if it wasn't? Would you need to acquire a lock wherever you used it (both while getting and setting)? – Iain Fraser Nov 07 '12 at 00:33
  • 2
    @IainFraser: Yes, if the collection isn't thread safe, you have to have a lock around *every* block of code that accesses it, and you have to use the same object as identifier (the `locker` object in the example) for all those locks. Also, the identifier has to have the same scope as the collection, i.e. if the collection is in a static variable, the identifier should also be static, and if the collection is in an instance member, the identifier should also be an instance member. In your example the collection is an instance member, but the identidier is in a static variable. – Guffa Nov 07 '12 at 00:37
  • Thanks for explaining that Guffa, it's much clearer now. To be sure, in the examples, identifier and collection are both static (unless I'm missing something). – Iain Fraser Nov 07 '12 at 00:42
  • @IainFraser: Yes, you are right, they are in fact both static in the example. I was confused by the fields standing so close to the method. :/ – Guffa Nov 07 '12 at 00:44
  • 1
    I would go for #2 and err on the side of precaution, but do all accesses to the collection inside the lock. Just save the existing or new value in a local variable and use that variable in the call to DoSomethingWithResult rather than accessing the collection again. – fsimonazzi Nov 07 '12 at 00:57
4

I'll introduce some sort of uncertainty, because the problem is not trivial. Basically I agree with Guffa and I'd choose second example. It's because the first is broken while third in turn, despite the fact is seems to be optimized, is tricky. That's why I'll focus on the third one here:

if (item == null)
{
    lock (_locker)
    {
        if (item == null)
            item = new Something();
    }
}

At first sight it may occur as improving performance without locking all the time, but there is also problematic, because of the memory model (reads may be reordered to come before writes), or aggressive compiler optimization (reference), for example:

  1. Thread A notices that the value item is not initialized, so it obtains the lock and begins to initialize the value.
  2. Due to memory model, compiler optimizations and so on, the code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization.
  3. Thread B notices that the shared variable has been initialized (or so it appears), and returns its value. Because thread B believes the value is already initialized, it does not acquire the lock. If the variable is used before A finishes initializing it, the program will likely crash.

There are solutions for that problem:

  1. You can defined item as a volatile variable, which assures that reading variable will be always up to date. Volatile is used to create a memory barrier between reads and writes on the variable.

    (see The need for volatile modifier in double checked locking in .NET and Implementing the Singleton Pattern in C#)

  2. You can use MemoryBarrier (item non-volatile):

    if (item == null)
    {
        lock (_locker)
        {
            if (item == null)
            {
                var temp = new Something();
                // Insure all writes used to construct new value have been flushed.
                System.Threading.Thread.MemoryBarrier();                     
                item = temp;
            }
        }
    }
    

    The processor executing the current thread cannot reorder instructions in such a way that memory accesses prior to the call to MemoryBarrier execute after memory accesses that follow the call to MemoryBarrier.

    (see Thread.MemoryBarrier Method and this topic)

UPDATE: Double-Check Locking, if implemented correctly, seems to be working fine in C#. For more details check additional references e.g. MSDN, MSDN magazine and this answer.

Community
  • 1
  • 1
jwaliszko
  • 16,942
  • 22
  • 92
  • 158
  • I wonder what advantage there is to having a compiler which can allow code to be resequenced outside a method? Even a source-code compiler in-lines a method when generating intermediate-language code, it should be able to annotate the generated code to let the JIT compiler know it shouldn't move code within the method outside it nor vice versa. Any performance a compiler could gain by migrating code through method boundaries would seem to be negated by the extra checking client code would have to do as a consequence, not to mention the countless man-hours of extra debugging chasing Heisenbugs. – supercat Aug 22 '13 at 19:25
  • @supercat: Say that you have three levels of functions. All of these small functions can be inlined. Two of the functions use the same redundant information, perhaps the length of a string. The read of the string length can be pulled out of both functions and placed at the top. This is very good for performance. Now with threading involved if barriers are not properly used another thread might delete characters from the string, invalidating the length. And while it may be a Heisenbug caused by optimization it could still happen without it because the programmer wrote an buggy program. – Zan Lynx Aug 23 '13 at 16:16
  • @ZanLynx: If I write `a=thing1.x; b=getsomething().x; c=thing1.x;` I don't have a problem with a compiler/JITter moving code *completely around* the method call, since the operations before and after the "call" are located near each other in the source code. Allowing the compiler/JITter to intersperse things *within* a function with things outside, though, is a recipe for either disaster or crummy performance (adding a memory barrier between the call to `getsomething` and the read of `x` would make things work, but would needlessly prevent operations from being moved *around* the method). – supercat Aug 23 '13 at 20:25
  • [of course, a JITter could only legitimately move code around a method call if it knew that the method did not include any memory barriers and could not throw exceptions which would cause such code motion to change program semantics, but when a non-virtual method call it may be possible to satisfy both constraints even without in-lining]. – supercat Aug 23 '13 at 20:29
3

I suggest you leave this problem to the pros and use the ConcurrentDictionary (I know I would). It has the GetOrAdd method that does exactly what you want and is guaranteed to work properly.

fsimonazzi
  • 2,975
  • 15
  • 13
  • Well, not exactly as the factory Func might be executed more than once, but I *really* suggest you consider using it. – fsimonazzi Nov 07 '12 at 00:34
  • Thanks fsimonazzi, I will add this to my box of tricks. In this case, I'm working with `System.Web.Caching.Cache`, so I have to take what I'm given – Iain Fraser Nov 07 '12 at 00:37
1

There are a variety of patterns one may use for lazy object creation, which is what your code examples seem to be focused on. Another variation that may sometimes be useful if your collection is something like an array or ConcurrentDictionary which allows code to atomically check whether a value has been already set and write it only if it hasn't, would be:

Thing theThing = myArray[index];
if (theThing == null) // Doesn't look like it's created yet
{
  Thing tempThing = new DummyThing(); // Cheap
  lock(tempThing) // Note that the lock surrounds the CompareExchange *and* initialization
  {
    theThing = System.Threading.Interlocked.CompareExchange
       (ref myArray[index], tempThing, null);
    if (theThing == null)
    {
      theThing = new RealThing(); // Expensive
      // Place an empty lock or memory barrier here if loose memory semantics require it
      myArray[index] = theThing ;
    }
  }
}
if (theThing is DummyThing)
{
  lock(theThing) { } // Wait for thread that created DummyThing to release lock
  theThing = myArray[index];
  if (theThing is DummyThing)
      throw something; // Code that tried to initialize object failed to do so
  }
}

This code assumes that it be possible to cheaply construct a dummy instance of a type derived from Thing. The new object should not be a singleton, nor otherwise reused. Every slot in myArray will be written twice--first with a pre-locked dummy object and then with the real object. Only one thread will be able to write a dummy object, and only the thread that successfully wrote a dummy object will be able to write the real one. Any other thread will either see a real object (in which case the object is fully initialized) or else a dummy object which will be locked until the array has been updated with a reference to the real one.

Unlike the other approaches shown above, this approach will allow the simultaneous initialization of different items in the array; the only time things will block is if an attempt is made to access an object whose initialization is in progress.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    I've read it a couple of times to be able to understand it. I'm still not really sure, so I have some questions. First, is the usage of `Interlocked.CompareExchange` required as we are already inside the synchronized block? Isn't this replacement: `theThing = myArray[index]; myArray[index] = myArray[index] ?? tempThing;` enough, instead of this: `theThing = Interlocked.CompareExchange(ref myArray[index], tempThing, null);`? The next doubt is related to `lock(theThing) { }` construction. How is it working as I see no other places of locking on `theThing` object? – jwaliszko Aug 23 '13 at 16:38