Suppose I have a std::atomic<std::map<std::string, std::string>>
and different threads do operations like std::string foo = map["bar"];
and map["bar"] = "baz"
. I don't save references or pointers to the values in the map. I just need storing and retrieving values to happen thread-safely. Does this work, or do I need to use a lock or such?
Suppose I have a std::atomic<std::map<std::string, std::string>>
and different threads do operations like std::string foo = map["bar"];
and map["bar"] = "baz"
. I don't save references or pointers to the values in the map. I just need storing and retrieving values to happen thread-safely. Does this work, or do I need to use a lock or such?
2 Answers
Reset to default 6Suppose I have a
std::atomic<std::map<std::string, std::string>>
Then your program is ill-formed. Except for specialisations defined by the standard, std::atomic<T>
requires that T
be TriviallyCopyable, CopyConstructible and CopyAssignable. No specialisation of std::map
satisfies TriviallyCopyable.
Even if you were allowed to form a std::atomic<std::map<std::string, std::string>> map;
, you wouldn't be able to do std::string foo = map["bar"];
nor map["bar"] = "baz"
.
The former would need to be something like std::string foo = map.load()["bar"];
, and the latter auto local = map.load(); local["bar"] = "baz"; map.store(local);
to be well formed, let alone express atomic semantics. The load is thread safe (ignoring that you lose the insertion of a default string if "bar"
is absent), but the store is not, as something else can modify the map between the two accesses.
There is no std::atomic<std::map<S, T>>
, hence it cannot be thread-safe to read or write from it. But let's go a step back and consider what std::atomic
gives us.
Generally std::atomic<T>
allows to load and store the value as a whole in a thread-safe way. Load and store are each atomic. No other thread can interfere with a load
nor a store
.
However, calling operator[]
on a std::map
is not just a store nor a load. Ergo atomic access to the map as a whole is not sufficient.
Here
std::map<std::string,std::string> x;
std::string foo = x["bar"];
the second line looks up the value in the map for the given key, if no value exists it inserts one, then it returns a reference to the mapped value. std::atomic::load
returns a copy, hence a wrong (!) implementation of the semantics of operator[]
in terms of load
and store
would be
auto m = map.load();
std::string foo = m["bar"];
map.store(foo);
While the load
and the store
individually are atomic and thread-safe, the operation as a whole is not. std::atomic
simply does not help here.
Last but not least, std::atomic<T>
requires T
to be TriviallyCopyable, but a std::map
is not TriviallyCopyable.
map
is anstd::atomic
thenmap["bar"]
will simply not compile – 463035818_is_not_an_ai Commented Mar 12 at 9:59std::atomic<std::map<std::string, std::string>>
will not give you hardware support and whole advantage of it is lost. If you do not know this this means you should not use this. This is not accident that talk about using std::atomic has "juggling with razors" in it. So I would recommend master other multi threading primitives before usingstd::atomic
. – Marek R Commented Mar 12 at 10:11std::atomic<std::map<std::string, std::string>>
" You don't. Not in the C++ language. That won't compile. – Drew Dormann Commented Mar 13 at 19:26