45

Is reference to object in std::map is thread safe?

std::map< std::string, Object >   _objects;

map can be changed from many threads and this access is synchronized, but reference to value (Object &) accessable just from 1 instance and thread. is write operations with Object & is safe if another thread will add items to map? will it reallocate?

dr11
  • 5,166
  • 11
  • 35
  • 77

2 Answers2

42

The C++11 standard guarantees that const method access to containers is safe from different threads (ie, both use const methods).

In addition, [container.requirements.dataraces] states

implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence, excepting vector<bool>

In other words, except for vector<bool> modifying distinct contents is not a data race.

Now, if one thread invalidates an iterator used by another thread, clearly this is a data race (and results in undefined behavior). If one thread does non-const access to a container, and another does const access, that is a data race (and undefined behavior). (Note: a number of functions are "considered const" for the purpose of multithreading, including begin, end and other functions (and methods) that are non-const simply because they return non-const iterators. [] is included in this set of pseudo-const for thread safety reasons, except for map and unordered_set etc -- 23.2.2.1).

However, it appears that if you have a reference to an element within the container, and engage in operations that do not invalidate that reference in another thread, and never write to that element in another thread, you can safely read from that reference. Similarly, if other threads never even read from the element, then writing to that element shouldn't result in undefined behavior.

For standards references, 17.6.5.9.5 seems to guarantee that functions from the standard library won't run away and read/write elements needlessly.

So the short answer: you are safe, so long as the other thread doesn't directly mess with that particular entry in the map.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    see [container.requirements.dataraces]/3. That ensures distinct elements can be modified concurrently, and as you say the element won't be modified by updates to the map in other threads, as stated by [associative.reqmnts]/9 – Jonathan Wakely Feb 25 '13 at 13:06
  • You certain there isn't a tortured reading? Concurrent access to elements is safe, using `const` members is safe, some non-`const` members won't invalidate references or iterators -- but are non-`const` members guaranteed to not *read* (and thus cause a data race with element writes) when doing innocuous operations on the container? I see no reason for a container to do this, but I see nothing in 23.2.2 that disallows it. – Yakk - Adam Nevraumont Feb 25 '13 at 14:28
  • A `std::map` should only read an element's key (because it doesn't know what to do with the rest of the element, so has no reason to look at it) and the key is `const`, so although I don't think there's explicit wording saying so, there's no reason for any member of `std::map` to read any non-const part of an element while modifying the map. – Jonathan Wakely Feb 25 '13 at 14:38
  • 17.6.5.9.2 -- "A C++ standard library function shall not directly or indirectly access objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s arguments, including this." while the `std::pair` is accessed, the `.second` member isn't, so the standard library cannot access it or cause it to be accessed (Similarly via 17.6.5.9.3, cannot modify it). I'm satisfied. – Yakk - Adam Nevraumont Feb 25 '13 at 14:41
  • I don't think that applies, the container's elements can always be accessed indirectly via `this`, and the `second` member can be accessed indirectly via a reference to an element. That rule says the map won't look at global data or data members of other objects without synchronization. It can always look at its own members, and anything reachable from those members. – Jonathan Wakely Feb 25 '13 at 14:42
  • 1
    17.6.5.9.5 then? "required by its specifications" might cover this. – Yakk - Adam Nevraumont Feb 25 '13 at 15:10
  • So in short: if you only read elements you are ok? Isn't that the same as being **not** thread safe....? – Michel Jan 28 '22 at 08:49
  • @Michel Thread safe is a relational property. Two pieces of code a thread safe relative to each other. No piece of code is thread safe by itself, because thread safe is relational. It is possible that two "read" operations on two different data structures are *not* thread safe relative to each other; the `std` guarantees this doesn't hold for `std` data structures. This actually required implementation changes in at least one case! Many people use thread safe as a property of code, not relational; this is one of the reason why threaded code tends to be buggy and suck. – Yakk - Adam Nevraumont Jan 28 '22 at 14:17
  • No need to educate me on parallel education, i know exactly how it works. Maybe I am misreading the accepted answer but I think that only being safe when reading is not really what is considered thread safe. The sentence 'if one thread invalidates an iterator used by another thread, clearly this is a data race (and results in undefined behavior)' is my problem here. Yes, I understand that multiple readers are allowed but one reader and a writer which deletes an element causes undefined behaviour. Calling this thread safe is misleading to say the least! – Michel Jan 30 '22 at 18:57
  • @michel Again, I am describing what operations are pairwise threadsafe. I understand your confusion, but do not share it. Your model of threadsafe doesn't model how the `std` library does it, the `std` library is very low level in what operations exactly are permitted. As a developer, you can wrap `std` constructs in locks and mutexes to provide a ldifferent level of safety; doing so has costs, and doing it efficiently and correctly will depend on understanding the `std` and C++ memory models. But I described what you can and cannot do without such locks. – Yakk - Adam Nevraumont Jan 31 '22 at 01:30
16

Elements in a map are stable, they do not get moved or invalidated unless the element is erased from the map. If only one thread is writing to a given object, and changes to the map itself are correctly synchronized, then I believe it will be safe. I'm sure it's safe in practice, and I think it's safe in theory too.

The standard guarantees that distinct elements can be modified by different threads, in [container.requirements.dataraces]

Notwithstanding (17.6.5.9), implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence, excepting vector<bool>, are modified concurrently.

This only allows you to modify the elements, not to insert new elements into the map while modifying elements. For some containers, such as std::vector, modifying the vector itself might also modify elements by reallocating and moving them, but [associative.reqmts]/9 ensures std::map won't invalidate existing elements.

Since no member function of std::map is required to access the second member of its elements (i.e. the mapped_type) I think [res.on.data.races]/5 says no other thread will conflict with writes to that member when modifying the map. (Thanks to Yakk for that final piece of the puzzle)

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • i keep reference to the value in thread1 (and just in this thread), but add another values with another key from another threads – dr11 Feb 25 '13 at 12:45
  • Is it guaranteed that inserting a new element into the map won't interfere with concurrent reader threads (accessing const)? Is it guaranteed that there is no restructuring in the red/black tree? What if the insert causes nodes to split while a concurrent thread performs a traversal through that same path? – dimstamat Aug 29 '20 at 16:22
  • 1
    @dimstamat, no, not at all guaranteed. Inserting a new element is a non-const operation, so conflicts with any other operation, including const ones. Read the other answer, which states it quite clearly. – Jonathan Wakely Aug 30 '20 at 17:17
  • That's right! That's what I understood, but I was confused by the last part of the answer. Thanks for the clarification! – dimstamat Aug 30 '20 at 23:22