2

I know in multithreading program, we need to use ConcurrentDictionary, ConcurrentBag etc those thread-safe collection. But in my situation, the number of keys in Dictionary is fixed, I have exact 5 keys which I already know before the program executes so I can initialize the dictionary's key. So my thinking is, because the number of the keys is not going to change, I can actually use Dictionary instead of ConcurrentDictionary, and the reason I am thinking to do this, is because the collection won't resize internally, so there won't be a situation when thread1 try to update an element after thread2 adds a new element then cause resizing, which makes thread1's update fail. Is my understanding correct?

More information:

I don't have a shared key/value pair that can be updated by all threads, each thread just update a particular key/value pair and TKey is a unique string, TValue is simple class type

  • premature (and incorrect) optimisation. If you will hit it with multiple threads then you need to coordindate those threads. Have you measured a performance bottleneck? – Mitch Wheat Mar 21 '22 at 05:42
  • @MitchWheat do I still need to coordindate those threads? may I ask why? there is no need of thread locking/unlocking because I don't have a shared key/value pair that can be updated by all threads, each thread just update a particular key/value pair –  Mar 21 '22 at 05:54
  • What is the type of the `TKey` and the `TValue` that you intend to use in the `Dictionary`? – Theodor Zoulias Mar 21 '22 at 06:17
  • @TheodorZoulias Tkey is a unique string, Tvalue is simple class type –  Mar 21 '22 at 06:21
  • Why don't you use one dictionary per thread then? – Kisar Mar 21 '22 at 06:37
  • @Kisar I can't, I need the main thread to access the dictionary to log sth –  Mar 21 '22 at 06:42
  • 2
    "each thread just update a particular key/value pair" - that sounds like you just need a `ThreadLocal`... unless multiple threads might use the same key? It's unclear, to be honest. Is there any reason you don't want to just use `ConcurrentDictionary` anyway? – Jon Skeet Mar 21 '22 at 07:03
  • If the collection is read-only, you don't need synchronization. If the collection is mutable then you need synchronization (e.g. `ConcurrentDictionary`). In this case you must expect that a thread can replace the value of a key and another thread reads the old value due to the race condition. Note that you can modify a `Dictionary` by overwriting an existing value (add and update). There is a `ReadOnlyDictionary`. If your scenario allows you to use it, you don't need the `ConcurrentDictionary`. But to add robustness, you should use the `ReadOnlyDictionary` instead of the mutable `Dictionary`. – BionicCode Mar 21 '22 at 08:33

2 Answers2

3

The documentation of the Dictionary<TKey, TValue> class states explicitly that:

To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

So, based on the documentation, if you modify the dictionary by multiple threads without synchronization, you have entered the "undefined behavior" territory. Meaning that "anything" can happen, and whatever happens will not be a bug. The warranty has been breached, and Microsoft couldn't care less about any damages that have occurred to you, after using their products incorrectly.

That said, and knowing how the Dictionary<TKey, TValue> class is implemented, it's unlikely that you'll have any issues by using a Dictionary<TKey, TValue> in a lock-free manner, if you follow the very strict usage pattern that you describe in your question. It's up to you decide if it's OK to rely on implementation details of the API that you use, instead of the published documentation.

As a side note, be aware that in general searching serially for a value in a small List<T> or array outperforms searching for a value in a small hash-based Dictionary<TKey, TValue>. The tipping point depends on the type of the key, and might be as large as 50 elements or more. Also for storing one value per thread, you might find the ThreadLocal<T> class useful.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 4
    *if you follow the very strict usage pattern* - the thing that worries me about one dev being very careful to observe this, is that the new hire with no knowledge/experience of the careful arrangement then *doesnt* follow it, it sneaks through review and prod starts acting very strangely.. – Caius Jard Mar 21 '22 at 07:16
  • @TheodorZoulias Given the information, you should not recommend the `Dictionary` in a concurrent read/write scenario. – BionicCode Mar 21 '22 at 10:52
2

Summary

  1. It will work.
  2. Relying on current implemenation is considered bad practice. Avoid it if you can.
  3. Optimising early is considered bad practice. Use a safest option and reassess only if confirmed too slow.
  4. A shared dictionary seem like a interesting choice here: it doesn't seem to add any functionality to your solution. A different solution (e.g. 5 variables) would work better here.

I really want it. Can I? Yes, but.

If

  1. the dictionary keys never change and
  2. only one thread accesses value for a given key

then locking in the current implementation is not needed (i.e. Dictionary is fine) because the dictionary itself doesn't change and a single consumer accesses each value.

Is it OK? Not really

Generally, it is not OK to use code which works but uses structures that have better alternatives for a given scenario. Code like this is very easy to break without knowing until it is deployed in a real life scenario and even then it's hard to find out the issue quickly.

tymtam
  • 31,798
  • 8
  • 86
  • 126
  • There is a `ReadOnlyDictionary` you must use to guard against accidental manipulation. This is the only safe and acceptable solution in a multithread environment. Don't rely on conventions here. – BionicCode Mar 21 '22 at 08:48