I am reading about thread safety of various stl containers from this link
Now I came across this point which states for C++11 only
Different elements in the same container can be modified concurrently
by different threads, except for the elements of std::vector<bool>
(for example, a vector of std::future objects can be receiving values
from multiple threads)
Does this mean if I have a method such as this which is being used by multiple
threads simultaneously (notice the method does not have any locks)
void ChangeValue(int index , int value)
{
someVector[index] = value;
}
Is the above method safe. My understanding is that it is safe for C++11 only.
However when I look at the other statement mentioned in the link
All const member functions can be called concurrently by different
threads on the same container. In addition, the member functions
begin(), end(), rbegin(), rend(), front(), back(), data(), find(),
lower_bound(), upper_bound(), equal_range(), at(), and, except in
associative containers, operator[], behave as const for the purposes
of thread safety (that is, they can also be called concurrently by
different threads on the same container). More generally, the C++
standard library functions do not modify objects unless those objects
are accessible, directly or indirectly, via the function's non-const
arguments, including the this pointer.
I come to the conclusion that in C++03 the above method can be safely used as well.
Kindly let me know if my understanding is correct.
It is meaningless to ask whether something is thread-safe under the C++03 standard - C++03 and earlier didn't have any concept of threads or thread safety.
ChangeValue is data race-free (as defined by C++11 and later) as long as no two threads pass the same argument for index, or else calls passing the same argument are synchronized with each other by some means external to the function.
Related
I'm coding with std::map in C++11.
I've read this link, which told me that the C++11 standard guarantees that const method access to containers is safe from different threads.
As my understanding, it means that std::map::size() is thread-safe, because this function is declared as size_type size() const noexcept;.
Now I want to call the function std::map::find thread-safely. For example, if (mp.find(xxx) != mp.end()) {}. However, there are two versions of find, one is const and the other is non-const:https://en.cppreference.com/w/cpp/container/map/find.
So how do I know which version of find is calling? How can I force the const-version find to be called in order to get a thread-safe code?
I knew there was a const version of std::map::cend(), will if (mp.find(xxx) != mp.cend()) {} work as expected?
You could have used std::as_const.
if(std::as_const(mp).find(xxx)==mp.end())
{
//do your thing
}
The general rule is that const is multiple-reader safe, and other operations are not.
But a number of exceptions are carved out. Basically, the non-const operations returning iterators or references to existing objects (no object is created) are also considered "reader" operations, and are multiple-reader safe. Using the non-const_iterators they return to modify the underlying data often has issues, but in many containers such modifications only cause contention if done on the same element that another operation is accessing.
So
if (map.find(foo) == map.end())
is safe to use with other operations that only read from the map object.
There are still good reasons to call the const operations. In c++17 there is std::as_const, permitting
if (std::as_const(map).find(foo) == map.cend())
which only calls const methods on map. You can write your own as_const easily:
template<class T>
std::add_const_t<T>& as_const( T& t ) { return t; }
(in c++11 you'll need to expand add_const_t). The std version adds
template<class T>
void as_const( T const&& t ) = delete;
to block some relatively pathological cases.
...
When thinking about thread safety, realize that it is a relational property. Two operations are relatively thread safe.
In the case of std containers, you have to think about how the operation reads or writes the container itself, which elements it reads or writes. While the standard is more technical, that will give you an intuitive understanding of what is allowed.
Methods that only read the container and return iterators or references to elements are sometimes non-const, but they themselves only read the container.
Operations that mutate or read elements are often only exclusive with other operations that mutate that element. In a vector, you are free to v[0] = 77; while another thread reads v[7] with no synchronization (outside of vector<bool>). But the moment you .push_back with insufficient capacity, you need to be synchronized with every other read.
This can be counter intuitive. Be careful, and read the documentation, if you are doing synchonization free access to a container. The intuition is only a first step.
If mp is const then find will be the const version. However calling the non-const version is no different in terms of thread safety than calling the const version, the difference comes if you actually use the returned iterator to make modifications to the held value (if you read the answer you linked carefully it contains this distinction).
Note that only calling multiple const methods is thread safe, calling a non-const method from another thread at the same time is not thread safe.
If I've understood correctly, since C++11 it has been safe to call const member functions of a container concurrently and modify the elements of a container as long as the container itself is not modified as part of the operation (as seen from e.g. the table concerning thread safety in cppreference.com). Since std::valarray is not listed in the containers section of the (draft) standard, I'm unsure if thread safety also applies to it. In other words,
Is it safe to read from a std::valarray concurrently (in particular by using operator[] with slices)?
Is it safe to modify the elements of std::valarray<T> concurrently if the operation on T is safe?
I would like to use std::valarray for a multidimensional array of numbers that would be filled using multiple threads.
If I am reading your question correctly, [res.on.data.races] protects distinct slices from participating in data races, under
A C++ standard library function shall not directly or indirectly
access objects accessible by threads other than the current thread
unless the objects are accessed directly or indirectly via the
function's arguments, including this.
[container.requirements.dataraces] adds extra protection around modifications to distinct elements, which strictly valarray lacks.
I have a std::vector<std::atomic<size_t>> vec. Is it safe to run vec[index].fetch_add(1, std::memory_order_release) or store/load with multiple concurrent threads on it? I think it should be, because reading is thread safe and writing to one entry at the same time from multiple threads is impossible because of the atomics - is that right?
No it is not, in general, thread safe since the container itself is not atomic.
That said, so long as you don't change what is in the vector (i.e. doing anything that invalidates the return of data()) , you'll be fine.
Sadly you can't resort to std::atomic<std::vector<...>> as a std::vector is not trivially copyable.
vec[X].fetch_add is safe as long as you're modifying each individual atomic. If you call any non-const (modifying) method of the wrapping std::vector this is undefined behaviour, as std::vector itself is not thread safe.
De-facto, you can initialize the vector in some thread, pass its reference to some asynchronous task and from that point, only act on specific elements of the vector, not act on the vector itself. vec[X].action(...) is thread safe, vec.action(...) is not.
It depends what the other threads are doing.
From the standard:
17.6.5.9 Data race avoidance [res.on.data.races]
...
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.
3 A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads
other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const
arguments, including this.
and
23.2.2 Container data races [container.requirements.dataraces]
1 For purposes of avoiding data races (17.6.5.9), implementations shall consider the following functions to be
const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at
and, except in associative or unordered associative containers, operator[].
2 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 container, excepting vector<bool>, are modified concurrently.
By combining these two parts with the rules for atomics, we can deduce that calling vec[index].fetch_add(1, std::memory_order_release) cannot cause a race condition with other threads performing the same or other "const" operations (including those mentioned in paragraph 23.2.2.1). However, if another thread invokes a non-const operation on vec itself (e.g. insert, erase, resize etc.) then we experience undefined behaviour as specified by section 1.10:
1.10 Multi-threaded executions and data races [intro.multithread]
...
4 Two expression evaluations conflict if one of them modifies a memory location (1.7) and the other one accesses
or modifies the same memory location.
...
21 The execution of a program contains a data race if it contains two conflicting actions in different threads,
at least one of which is not atomic, and neither happens before the other. Any such data race results in
undefined behavior.
Your expression vec[n].atomic_op(...) is not itself atomic, but decomposes into:
auto iter = vec.begin();
iter += n;
iter->atomic_op(...);
So, the first two statements are vulnerable to the usual iterator invalidation rules. Concurrently changing the size of the vector, or erasing any element before the nth, or the nth element itself, can break them.
Once you have an iterator to an element, if that iterator is not invalidated during the time you're using it, then whatever atomic operations you perform on the element itself will be safe.
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?
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.
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)
I have trouble finding any up-to-date information on this.
Do C++11 versions of STL containers have some level of thread safety guaranteed?
I do expect that they don't, due to performance reasons. But then again, that's why we have both std::vector::operator[] and std::vector::at.
Since the existing answers don't cover it (only a comment does), I'll just mention 23.2.2 [container.requirements.dataraces] of the current C++ standard specification which says:
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.
i.e. it's safe to access distinct elements of the same container, so for example you can have a global std::vector<std::future<int>> of ten elements and have ten threads which each write to a different element of the vector.
Apart from that, the same rules apply to containers as for the rest of the standard library (see 17.6.5.9 [res.on.data.races]), as Mr.C64's answer says, and additionally [container.requirements.dataraces] lists some non-const member functions of containers that can be called safely because they only return non-const references to elements, they don't actually modify anything (in general any non-const member function must be considered a modification.)
I think STL containers offer the following basic thread-safety guarantee:
simultaneous reads of the same object are OK
simultaneous read/writes of different objects are OK
But you have to use some form of custom synchronization (e.g. critical section) if you want to do something different, like e.g. simultaneous writes on the same object.
No. Check out PPL or Intel TBB for thread safe STL-like containers.
Like others have noted they have usual "multiple reader thread safety" but that is even pre C++11. Ofc this doesnt mean single writer multiple readers. It means 0 writers. :)