dvs23
dvs23

Reputation: 137

c++ - vector of atomics fully thread safe?

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?

Upvotes: 5

Views: 1715

Answers (4)

Stewart Becker
Stewart Becker

Reputation: 308

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.

Upvotes: 3

David Haim
David Haim

Reputation: 26476

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.

Upvotes: 4

Useless
Useless

Reputation: 67723

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.

Upvotes: 2

Bathsheba
Bathsheba

Reputation: 234655

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.

Upvotes: 6

Related Questions