Saturday, December 7, 2013

ConcurrentDictionary.GetOrAdd and Thread Safety

.NET 4.0 added the awesome System.Collections.Concurrent namespace, which includes the very useful ConcurrentDictionary class. While the ConcurrentDictionary is thread safe, it can experience problems with adding values during high concurrency...

ConcurrentDictionary.GetOrAdd may invoke the valueFactory multiple times per key.

This behavior will only happens under high load, and even if the valueFactory does get invoked multiple times the dictionary entry will only ever be set once. Normally this is not much of a problem. However, if you are using this Dictionary to store large or expensive objects (such as unmanaged resources or database connections), then the accidental instantiation of multiple of these could be a real problem for your application.

Don't worry, there is a very simple solution to avoid this problem: just create Lazy wrappers for your expensive objects. That way it will not matter how many times the valueFactory is called, because only one instance of the resource itself will ever actually be accessed and instantiated.

Sample Code

[Fact]
public void Failure()
{
    var dictionary = new ConcurrentDictionary<int, int>();
    var options = new ParallelOptions {MaxDegreeOfParallelism = 100};
    var addStack = new ConcurrentStack<int>();
 
    Parallel.For(1, 1000, options, i =>
    {
        var key = i % 10;
        dictionary.GetOrAdd(key, k =>
        {
            addStack.Push(k);
            return i;
        });
    });
 
    // There are 10 items in our dictionary...
    Assert.Equal(10, dictionary.Count);
 
    // ...but the add was invoked more than 10 times! :(
    Assert.True(10 < addStack.Count);
}
 
[Fact]
public void Success()
{
    var dictionary = new ConcurrentDictionary<int, Lazy<int>>();
    var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
    var addStack = new ConcurrentStack<int>();
    var lazyStack = new ConcurrentStack<int>();
 
    Parallel.For(1, 1000, options, i =>
    {
        var key = i % 10;
        dictionary.GetOrAdd(key, k =>
        {
            addStack.Push(k);
            return new Lazy<int>(() =>
            {
                lazyStack.Push(k);
                return i;
            });
        });
    });
 
    // Access the dictionary values to create lazy values.
    foreach (var pair in dictionary)
        Assert.NotNull(pair.Value.Value);
 
    // There are 10 items in our dictionary...
    Assert.Equal(10, dictionary.Count);
 
    // ...and the add was invoked more than 10 times...
    Assert.True(10 < addStack.Count);
 
    // ...however only 10 values were actually created! :)
    Assert.Equal(10, lazyStack.Count);
}
Shout it

Enjoy,
Tom

No comments:

Post a Comment

Real Time Web Analytics