Yes, this causes a problem. The wording from the standard (§ [intro.races]/21.2) is:
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
So, if you only read from multiple threads, you don't get "conflicting actions", but if even one thread writes, you need to synchronize the access.
In the specific case of unordered_map
, I can see a situation where writing and reading concurrently could lead to a really serious problem.
An unordered_map uses a hash table with collision chaining. That means if two (or more) keys hash to the same value, they're all stored in the same "bucket", which is basically a linked-list of the values. So, looking something up in an unordered_map
can involve traversing a linked list.
Experience shows that in a typical case, we can usually expect some elements in a collection to be accessed more often than others, often following something like the commonly known 80:20 rule (20% of the elements will account for 80% of the accesses).
That being the case, one way to optimize a hash table is to try to keep the most commonly accessed elements at the heads of their respective linked lists. To do that, writing a value could not only modify the value itself, but also move its node to (or toward) the beginning of the linked list. So, your reading thread could try to traverse the linked list just as the writing thread is trying to move a node forward in the linked list, so the linked list is completely corrupt at the time (e.g., contains a next
pointer that hasn't been initialized yet).
So even when you don't do any insertion of deletion, and change the value in the map from bool
to std::atomic<bool>
, you still have a potential race condition. You really need to use a lock to protect access to the map itself.