42

If I initialize a generic dictionary once, and no further adds/updates/removes are allowed, is it safe to have multiple threads reading from it with no locking (assuming that the dictionary is initialized before the readers are started)?

There is a note in the help for the non-generic HashTable that says that it is safe for multiple readers, but I did not see something similar for the Generic Dictionary.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
JMarsch
  • 21,484
  • 15
  • 77
  • 125

2 Answers2

68

For your future reference, the documentation is here:

http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

It says:

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 8
    Just want to make sure I understand that "Even so ..." part of the quoted paragraph correctly. Is it true that: (1) if the dictionary won't be modified anymore (as mentioned in the OP's question), then the enumerating won't be a problem either. (2) if the dictionary will potentially be modified in the future, then even the `ConcurrentDictionary` enumeration is "intrinsically not a thread-safe procedure" either. Correct? – RayLuo Sep 06 '17 at 00:18
  • 1
    @RayLuo: In general you are not allowed to modify a collection while an enumeration is "in flight" regardless of whether it is multithreaded or single threaded. – Eric Lippert Sep 06 '17 at 00:36
  • 1
    Concurrent framework collections (e.g., `System.Collections.Concurrent.ConcurrentDictionary`) generally do allow modifications during enumeration. However, they accomplish this by making a copy of the collection and having you enumerate the copy. And the entire collection is locked while the copy is made. A framework engineer might argue that this means you're not modifying the collection while the enumeration is "in flight" (since under the hood, the whole thing was locked while the enumeration was "in flight"), but from a framework user's perspective, the enumeration is in flight. – Brian Sep 06 '17 at 14:09
  • Thanks for the insight, Eric & @Brian. For what it's worth, here is quote from the [Dictionary.GetEnumerator()](https://msdn.microsoft.com/en-us/library/9c6ftx8b.aspx): "An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined. The enumerator does not have exclusive access to the collection;" ConcurrentDictionary enumerates [differently](https://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx#Remarks), BTW. – RayLuo Sep 07 '17 at 00:34
  • I don't understand why "modifying" could be a problem. – The incredible Jan Mar 23 '23 at 06:59
  • @TheincredibleJan: The enumerator enumerates key/value pairs. Suppose you have a dictionary with a pair "X"->"XRAY", you create an enumerator, and then you modify the dictionary so that "X" keys "XYLOPHONE" instead. When you step the enumerator, what happens? Do you get the key-value pair that existed when the *enumerator* was created, or the pair that is *currently* in the dictionary? There's a reasonable argument to be made for both, and reasonable implementation choices that lead to both. So neither choice is guaranteed. – Eric Lippert Mar 27 '23 at 18:58
  • @TheincredibleJan: If you *require* a dictionary to have documented, well-defined behaviours when dealing with a dictionary that may be modified mid-enumeration, then it is incumbent upon you to specify, document, implement and test those behaviours, not incumbent upon the author of the library Dictionary class. – Eric Lippert Mar 27 '23 at 18:59
23

Yes, it's safe if you don't modify the dictionary any more. Thread safety is only an issue in read/write scenarios

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 11
    You have to take internal state into account, though. Externally, you may only be reading a value from the dictionary. However, however, you do not know what state transitions may occur during the retrieval. – JMarsch Jun 05 '12 at 15:31
  • OP @JMarsch commented above and received 10 up-votes. So how does that "internal state" thing would affect the OP's scenario? Is it still OK that `If I initialize a generic dictionary once, and no further adds/updates/removes are allowed, is it safe to have multiple threads reading from it with no locking (assuming that the dictionary is initialized before the readers are started)?` – RayLuo Sep 06 '17 at 00:13
  • For a dictionary, yes, the answer from Eric Lippert stands. The important thing to remember from my comment is that in general, it is not enough to assume that a data structure is threadsafe if you only do reads on it, because you don't know how the read affects internal state. In the case of the dictionary, it turns out that read operations really are safe so long as no one ever writes to it while concurrent readers are using it. So in your example, where you initialize it once, and then only read from it, you are safe. – JMarsch Sep 06 '17 at 15:12
  • 1
    Thanks for the clarification. So the actionable take-away from your first comment is then "ideally, double check with the data structure's documentation or source code, to see whether reads are thread-safe", because, intuitively one would assume reads are thread-safe, so when that's not the case, the data structure developer would hopefully document it. (For the sake of completeness, I did a mental exercise and figured a huffman tree which can dynamically adjust its structure based on read frequency, is possibly not thread-safe for read. Perhaps those 10 upvoters also did their exercise too.) – RayLuo Sep 07 '17 at 00:16
  • There is no "internal state" stuff if you have direct access to member values and not only by getters. – The incredible Jan Mar 23 '23 at 07:04