I have this function:
static Dictionary<int, int> KeyValueDictionary = new Dictionary<int, int>();
static void IncreaseValue(int keyId, int adjustment)
{
if (!KeyValueDictionary.ContainsKey(keyId))
{
KeyValueDictionary.Add(keyId, 0);
}
KeyValueDictionary[keyId] += adjustment;
}
Which I would have thought would not be thread safe. However, so far in testing it I have not seen any exceptions when calling it from multiple threads at the same time.
My questions: Is it thread safe or have I just been lucky so far? If it is thread safe then why?
You’re getting lucky. These types of bugs with threads are so easy to make because testing can you give you a false sense of security that you did things correctly.
It turns out that
Dictionary<TKey, TValue>is not thread-safe when you have multiple writers. The documentation explicitly states:Alternatively, use
ConcurrentDictionary. However, you still must write correct code (see note below).In addition to the lack of thread-safety with
Dictionary<TKey, TValue>which you’ve been lucky to avoid, your code is dangerously flawed. Here’s how you can get a bug with your code:keyId = 17. As the Dictionary is empty, the conditional in theifreturnstrueand thread 1 reaches the line of code markedA.keyId = 17. As the Dictionary is empty, the conditional in theifreturnstrueand thread 2 reaches the line of code markedA.(17, 0)to the dictionary.(17, 0)to the dictionary. An exception is thrown because of a key violation.There are other scenarios in which an exception can occur. For example, thread 1 could be paused while loading the value of
KeyValueDictionary[keyId](say it loadskeyId = 17, and obtains the value42), thread 2 could come in and modify the value (say it loadskeyId = 17, adds the adjustment27), and now thread 1 resumes and adds its adjustment to the value it loaded (in particular, it doesn’t see the modification that thread 2 made to the value associated withkeyId = 17!).Note that even using a
ConcurrentDictionary<TKey, TValue>could lead to the above bugs! Your code is NOT safe for reasons not related to the thread-safety or lack thereof forDictionary<TKey, TValue>.To get your code to be thread-safe with a concurrent dictionary, you’ll have to say:
Here we are using
ConcurrentDictionary.AddOrUpdate.