C++11 Stl Containers and Thread Safety

C++11 STL containers and thread safety

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.)

Container Thread Safety

The main thread-safety rule of stl containers is that if more than one working thread is accessing a shared container, and at least one of them is non-const, then the threads should be synchronized. If you do not put any synchronizations, it will be undefined behavior.

if you take a look at the C++ reference here for std::vector::size(), it says:

Data Races

The container is accessed. No contained elements are accessed:
concurrently accessing or modifying them is safe.

As mentioned, the vector container will be accessed during the call to .size() and this access does not allow you to call non-const methods at the same time on the vector. If you push_back an element into the vector when you get the size of the vector by calling .size(), then the behavior of your program will be undefined.

C++11 / C++03 and std::vector thread safety

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.

Thread safety of C++ std Containers

Sounds about right.

Note that accessing values within the map from multiple threads, if you modify the actual value, will also need to be protected. If you KNOW that two threads update DIFFERENT entries (I don't mean inserting/removing), then it's safe.

C++ Standard Library container thread-safety with respect to contained objects


My question is if containers like vector, set, queue, map, multimap, etc. provide their standard thread-safety guarantees (i.e. that concurrent threads may access const members, etc.) regardless of how a thread accesses the contained object.

No, not "regardless". Given "concurrent threads [accessing] const members" [of the container], they can get const access to stored elements, but the container doesn't make it possible to do anything to the objects that wouldn't be legal if the objects were e.g. local variables - i.e. you can't call methods that affect mutable or static variables in a thread-unsafe way.

To put it simply: if you lock, say, a map for read, can you safely (as far as the map is concerned) modify the contained objects (in this case the value) as long as you're not inserting or deleting items or otherwise calling non-const methods on the map?

If by "lock a map for read" you mean your program has a separate read/write-lock and gets "reader" lock state before accessing the map, then no - you can't modify the contained objects if other readers may be accessing them. To make that safe, you need a mutex around the map usages, just as you would if the threads were operating on a local variable.


Examples

Below, a blank line separates examples, and the first and second columns list commands from two threads that may execute in either order or concurrently. Note that just because something is "safe" to execute doesn't mean the update will be visible in other threads until some explicit memory barrier or cache-flushing operation is done - it depends on your hardware: "proper" mutex/rwlocks etc. tend to take care of that.

class X { int n_; std::string s_; } x;

std::vector<X> v = ...;
std::map<int,X> m = ...;

thread 1 thread 2 safe?

v.push_back(...); ++v[0].n_; Precondition: 1 <= size() < capacity()
(i.e. safe iff v[0] can't be moved)

v.some-const-member(); v.another-const-member(); YES - e.g. [n], find(), begin()

v[0].s_ = "hi"; std::cout << v[0].s_; NO - as for any string var

v[0].s_.size(); std::cout << v[0].s_; YES - as for any string var

rw_lock.r_lock() LOCKED
iterator i = m.find(7); rw_lock.w_lock() BLOCK
rw_lock.r_unlock() ....
LOCKED
std::cout << i->second; m.insert(...); YES - insert can't invalidate i
i->second.n_ += 3; m.find(7).n_ -= 3; NO - as per any int var

C++11 Thread Safety of Atomic Containers

Just write operator += as:

    inline AtomicVariable& operator+=(AtomicVariable const &rhs) {
atomic += rhs.atomic;
return *this;
}

In documentation: http://en.cppreference.com/w/cpp/atomic/atomic operator += is atomic.

Your example fails because below scenario of execution is possible:

  1. Thread1 - rhs.atomic.load() - returns 10 ; Thread2 - rhs.atomic.load() - returns 100
  2. Thread1 - atomic.load() - returns 0 ; Thread2 - atomic.load - returns 0
  3. Thread1 - add values (0 + 10 = 10) ; Thread2 - add values (0 + 100)
  4. Thread1 - atomic.store(10) ; Thread2 - atomic.store(100)

Finally in this case in atomic value might be 10 or 100, depends of which thread first execute atomic.store.



Related Topics



Leave a reply



Submit