4

I'm wondering if there are any downsides to locking over a collection such as a List<T>, HashSet<T>, or a Dictionary<TKey, TValue> rather than a simple object.

Note: in the following examples, that is the only place where the locks occur, it's not being locked from multiple places, but the static method may be called from multiple threads. Also, the _dict is never accessed outside of the GetSomething method.

My current code looks like this:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
public static string GetSomething(string key)
{
    string result;
    if (!_dict.TryGetValue(key, out result))
    {
        lock (_dict)
        {
            if (!_dict.TryGetValue(key, out result))
            {
                _dict[key] = result = CalculateSomethingExpensive(key);
            }
        }
    }
    return result;
}

Another developer is telling me that locking on a collection will cause issues, but I'm skeptical. Would my code be more efficient if I do it this way?

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
private static readonly object _syncRoot = new object();
public static string GetSomething(string key)
{
    string result;
    if (!_dict.TryGetValue(key, out result))
    {
        lock (_syncRoot)
        {
            if (!_dict.TryGetValue(key, out result))
            {
                _dict[key] = result = CalculateSomethingExpensive(key);
            }
        }
    }
    return result;
}
ckknight
  • 5,953
  • 4
  • 26
  • 23
  • Did you consider moving the _dict and the GetSomething() into a separate class? It clearly does something different and unrelated to the rest of the class. (It looks like a Memoization pattern) – Sjoerd Oct 11 '10 at 23:52
  • You have to declare the _dict to be volatile to have a chance to get it right. See also this answer about Double-Checked Locking and its pitfalls: http://stackoverflow.com/questions/394898/double-checked-locking-in-net/394932#394932 – Sjoerd Oct 11 '10 at 23:55
  • @Sjoerd volatile wouldn't be necessary with C# double-checked in many cases. However, here double-checked won't work either way, as it's not double-checked field-read but double-checked method-call, which only works if the method called is atomic, which this isn't, so double-checked is just plain wrong here. – Jon Hanna Oct 12 '10 at 01:50
  • @Jon Hanna True. Which shows how many pitfalls double-checked locking has. – Sjoerd Oct 12 '10 at 10:38
  • @Sjoerd, yes. I'd generally consider it an optimisation rather than a "normal" technique, but then again the risk of deadlock can sometimes be mitigated by doing things that would normally be considered optimisations! Still, double-checked is always suspicious, and in this case so dangerous that my answer spends more time on that than on anything else. – Jon Hanna Oct 12 '10 at 10:52
  • @Jon Hanna, then what is an efficient, correct way to have a similar pattern? I haven't had any locking issues doing this before. – ckknight Oct 12 '10 at 17:02
  • Get rid of the prior check before the lock. It's not locking issues that are the risk here, its that you are calling a non-threadsafe member outside of the lock. – Jon Hanna Oct 12 '10 at 17:06

5 Answers5

13

If you expose your collections to the outside world, then, yes this can be a problem. The usual recommendation is to lock on something that you exclusively own and that can never be locked unexpectedly by code that is outside your influence. That's why generally it's probably better to lock on something that you'd never even consider exposing (i.e. a specific lock object created for that purpose). That way, when your memory fails you, you'll never probably not get unexpected results.

To answer your question more directly: Adding another object into the mix is never going to be more efficient, but placing what is generally regarded as good coding practice before some perceived, but unmeasured efficiency might be an optmisation occurring prematurely. I favour best practice until it's demonstrably causing a bottleneck.

spender
  • 117,338
  • 33
  • 229
  • 351
  • In fact, when this happens, deadlock risk increases a lot!! – usr-local-ΕΨΗΕΛΩΝ Oct 11 '10 at 23:30
  • As I said, the collection is never exposed and only accessed from a single method. – ckknight Oct 11 '10 at 23:30
  • As long as you're positive that it's the case, then there's no problem. Because I'm stupid and forgetful, I always favour using an object specifically for locking. It's one less thing to worry about in a multi-threaded world that is fraught with peril. – spender Oct 11 '10 at 23:34
  • 3
    +1 for valueing good code practice over unmeasured efficiency. – Sjoerd Oct 11 '10 at 23:48
  • 4
    "An object for locking" is not always good enough, and can lead to deadlocks. What's needed is an object per related locking requirements, so that if a class has two sets of operations that need to be synchronised, but not with each other, it should have two such objects, and so on. A class could potentially need many such objects. When you see a lot of such objects though it's often an optimisation for very fine-grained locking rather than to ensure correctness (and begins making correctness harder in a different way). This can though lead to more complicated deadlocks. There's no fast rule. – Jon Hanna Oct 12 '10 at 09:27
6

In this case, I would lock on the collection; the purpose for the lock relates directly to the collection and not to any other object, so there is a degree of self-annotation in using it as the lock object.

There are changes I would make though.

There's nothing I can find in the documentation to say that TryGetValue is threadsafe and won't throw an exception (or worse) if you call it while the dictionary is in an invalid state because it is half-way through adding a new value. Because it's not atomic, the double-read pattern you use here (to avoid the time spent obtaining a lock) is not safe. That will have to be changed to:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
public static string GetSomething(string key)
{
    string result;
    lock (_dict)
    {
        if (!_dict.TryGetValue(key, out result))
        {
            _dict[key] = result = CalculateSomethingExpensive(key);
        }
    }
    return result;
}

If it is likely to involve more successful reads than unsuccessful (that hence require writes), use of a ReaderWriterLockSlim would give better concurrency on those reads.

Edit: I just noticed that your question was not about preference generally, but about efficiency. Really, the efficiency difference of using 4 bytes more memory in the entire system (since it's static) is absolutely zero. The decision isn't about efficiency at all, but since both are of equal technical merit (in this case) is about whether you find locking on the collection or on a separate object is better at expressing your intent to another developer (including you in the future).

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
3

No. As long as the variable is not accessible from anywhere else, and you can guarantee that the lock is only used here, there is no downside. In fact, the documentation for Monitor.Enter (which is what a lock in C# uses) does exactly this.

However, as a general rule, I still recommend using a private object for locking. This is safer in general, and will protect you if you ever expose this object to any other code, as you will not open up the possibility of your object being locked on from other code.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

To directly answer your question: no

It makes no difference whatever object you're locking to. .NET cares only about it's reference, that works exactly like a pointer. Think about locking in .NET as a big synchronized hash table where the key is the object reference and the value is a boolean saying you can enter the monitor or not. If two threads lock onto different objects (a != b) they can enter the lock's monitor concurrently, even if a.Equals(b) (it's very important!!!). But if they lock on a and b, and (a==b) only one of them at a time will be in the monitor.

As soon as dict is not accessed outside your scope you have no performance impact. If dict is visible elsewhere, other user code may get a lock on it even if not necessarily required (think your deskmate is a dumb and locks to the first random object he finds in the code).

Hope to have been of help.

usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305
0

I would recommend using the ICollection.SyncRoot object for locking rather than your own object:

    private static readonly Dictionary<String, String> _dict = new Dictionary<String, String>();
    private static readonly Object _syncRoot = ((ICollection)_dict).SyncRoot;
Steve Ellinger
  • 3,957
  • 21
  • 19
  • 2
    SyncRoot is only supported because it has to be if they weren't to break the definition of the ICollection interface. It's widely described as a mistake, which is why it wasn't included in ICollection. Existing code using it should be refactored to no longer depend on it. New code certainly shouldn't use it. – Jon Hanna Oct 11 '10 at 23:46
  • 1
    Thanks Jon, I had not heard this – Steve Ellinger Oct 12 '10 at 00:49
  • take a look at this answer about SyncRoot pattern: http://stackoverflow.com/a/12425979/355438 – Ilya Serbis Nov 25 '16 at 17:37