.NET - Dictionary locking vs. ConcurrentDictionary
A thread-safe collection vs. a non-threadsafe-collection can be looked upon in a different way.
Consider a store with no clerk, except at checkout. You have a ton of problems if people don't act responsibly. For instance, let's say a customer takes a can from a pyramid-can while a clerk is currently building the pyramid, all hell would break loose. Or, what if two customers reaches for the same item at the same time, who wins? Will there be a fight? This is a non-threadsafe-collection. There's plenty of ways to avoid problems, but they all require some kind of locking, or rather explicit access in some way or another.
On the other hand, consider a store with a clerk at a desk, and you can only shop through him. You get in line, and ask him for an item, he brings it back to you, and you go out of the line. If you need multiple items, you can only pick up as many items on each roundtrip as you can remember, but you need to be careful to avoid hogging the clerk, this will anger the other customers in line behind you.
Now consider this. In the store with one clerk, what if you get all the way to the front of the line, and ask the clerk "Do you have any toilet paper", and he says "Yes", and then you go "Ok, I'll get back to you when I know how much I need", then by the time you're back at the front of the line, the store can of course be sold out. This scenario is not prevented by a threadsafe collection.
A threadsafe collection guarantees that its internal data structures are valid at all times, even if accessed from multiple threads.
A non-threadsafe collection does not come with any such guarantees. For instance, if you add something to a binary tree on one thread, while another thread is busy rebalancing the tree, there's no guarantee the item will be added, or even that the tree is still valid afterwards, it might be corrupt beyond hope.
A threadsafe collection does not, however, guarantee that sequential operations on the thread all work on the same "snapshot" of its internal data structure, which means that if you have code like this:
if (tree.Count > 0)
Debug.WriteLine(tree.First().ToString());
you might get a NullReferenceException because inbetween tree.Count
and tree.First()
, another thread has cleared out the remaining nodes in the tree, which means First()
will return null
.
For this scenario, you either need to see if the collection in question has a safe way to get what you want, perhaps you need to rewrite the code above, or you might need to lock.
You still need to be very careful when using thread-safe collections because thread-safe doesn't mean you can ignore all threading issues. When a collection advertises itself as thread-safe, it usually means that it remains in a consistent state even when multiple threads are reading and writing simultaneously. But that does not mean that a single thread will see a "logical" sequence of results if it calls multiple methods.
For example, if you first check if a key exists and then later get the value that corresponds to the key, that key may no longer exist even with a ConcurrentDictionary version (because another thread could have removed the key). You still need to use locking in this case (or better: combine the two calls by using TryGetValue).
So do use them, but don't think that it gives you a free pass to ignore all concurrency issues. You still need to be careful.
Internally ConcurrentDictionary uses a separate lock for each hash bucket. As long as you use only Add/TryGetValue and the like methods that work on single entries, the dictionary will work as an almost lock-free data structure with the respective sweet performance benefit. OTOH the enumeration methods (including the Count property) lock all buckets at once and are therefore worse than a synchronized Dictionary, performance-wise.
I'd say, just use ConcurrentDictionary.