views:

81

answers:

5

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;
}
+2  A: 

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
+5  A: 

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
In fact, when this happens, deadlock risk increases a lot!!
djechelon
As I said, the collection is never exposed and only accessed from a single method.
ckknight
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
+1 for valueing good code practice over unmeasured efficiency.
Sjoerd
"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
+1  A: 

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.

djechelon
A: 

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
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<T>. Existing code using it should be refactored to no longer depend on it. New code certainly shouldn't use it.
Jon Hanna
Thanks Jon, I had not heard this
Steve Ellinger
http://blogs.msdn.com/b/brada/archive/2003/09/28/50391.aspx is one piece on it.
Jon Hanna
+4  A: 

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