Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConcurrentDictionary Pitfall - Are delegates factories from GetOrAdd and AddOrUpdate synchronized?

The documentation of ConcurrentDictionary doesn't explicit state, so I guess we cannot expect that delegates valueFactory and updateValueFactory have their execution synchronized (from GetOrAdd() and AddOrUpdate() operations respectively).

So, I think we cannot implement use of resources inside them which need concurrent control without manually implementing our own concurrent control, maybe just using [MethodImpl(MethodImplOptions.Synchronized)] over the delegates.

Am I right? Or the fact that ConcurrentDictionary is thread-safe we can expect that calls to these delegates are automatically synchronized (thread-safe too)?

like image 520
Luciano Avatar asked May 07 '12 17:05

Luciano


People also ask

Is ConcurrentDictionary GetOrAdd thread safe?

The GetOrAdd functionThe vast majority of methods it exposes are thread safe, with the notable exception of one of the GetOrAdd overloads: TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory); This overload takes a key value, and checks whether the key already exists in the database.

What is a ConcurrentDictionary?

ConcurrentDictionary is thread-safe collection class to store key/value pairs. It internally uses locking to provide you a thread-safe class. It provides different methods as compared to Dictionary class. We can use TryAdd, TryUpdate, TryRemove, and TryGetValue to do CRUD operations on ConcurrentDictionary.

Is ConcurrentDictionary AddOrUpdate thread safe?

It is thread safe in your usage. It becomes not thread safe when the delegate passed to AddOrUpdate has side effects, because those side effects may be executed twice for the same key and existing value.


2 Answers

Yes, you are right, the user delegates are not synchronized by ConcurrentDictionary. If you need those synchronized it is your responsibility.

The MSDN itself says:

Also, although all methods of ConcurrentDictionary are thread-safe, not all methods are atomic, specifically GetOrAdd and AddOrUpdate. The user delegate that is passed to these methods is invoked outside of the dictionary's internal lock. (This is done to prevent unknown code from blocking all threads.)

See "How to: Add and Remove Items from a ConcurrentDictionary

This is because the ConcurrentDictionary has no idea what the delegate you provide will do or its performance, so if it attempted lock around them, it could really impact performance negatively and ruin the value of the ConcurrentDictionary.

Thus, it is the user's responsibility to synchronize their delegate if that is necessary. The MSDN link above actually has a good example of the guarantees it does and does not make.

like image 99
James Michael Hare Avatar answered Oct 05 '22 18:10

James Michael Hare


Not only are these delegates not synchronized, but they are not even guaranteed to happen only once. They can, in fact, be executed multiple times per call to AddOrUpdate.

For example, the algorithm for AddOrUpdate looks something like this.

TValue value; do {   if (!TryGetValue(...))   {     value = addValueFactory(key);     if (!TryAddInternal(...))     {       continue;     }     return value;   }   value = updateValueFactory(key); }  while (!TryUpdate(...)) return value; 

Note two things here.

  • There is no effort to synchronize execution of the delegates.
  • The delegates may get executed more than once since they are invoked inside the loop.

So you need to make sure you do two things.

  • Provide your own synchronization for the delegates.
  • Make sure your delegates do not have any side effects that depend on the number of times they are executed.
like image 37
Brian Gideon Avatar answered Oct 05 '22 16:10

Brian Gideon