Search code examples
c++multithreadingthread-safetystddeque

Are concurrent calls to emplace_back() and operator[]() from std::deque thread safe?


An excerpt of the documentation from emplace_back():

  • Iterator validity

All iterators related to this container are invalidated, but pointers and references remain valid, referring to the same elements they were referring to before the call.

  • Data races

The container is modified.

No contained elements are accessed by the call: concurrently accessing or modifying them is safe (although see iterator validity above).

And an excerpt of the documentation from operator[]():

  • Data races

The container is accessed (neither the const nor the non-const versions modify the container).

Element n is potentially accessed or modified. Concurrently accessing or modifying other elements is safe.

So, given that some instance of a deque has at least one element, accessing it through operator[]() and calling emplace_back() on the container concurrently is indeed thread safe?

I'm inclined to say it is, but can't decide if "accessing" in emplace_back()'s docs comprises the use of operator[]() as in:

int access( std::deque< int > & q )
{
    return q[ 0 ];
}

void emplace( std::deque< int > & q , int i )
{
    q.emplace_back( i );
}

where both functions are called concurrently, or that "accessing" only applies to elements of which some reference or pointer has already been taken:

std::deque< int > q { 1 };

auto * ptr = & q[ 0 ]

std::thread t1 ( [ ptr  ]{ * ref = 0; } );
std::thread t2 ( [ & q ]{ q.emplace_back( 2 ); } );

edit: For further reference, here's what the C++ 14 standard (actually, the November 2014 Working Draft, N4296) states about insertions in deque with respect to references and iterators validity:

  • 23.3.3.4 deque modifiers

(...)

  1. Effects: An insertion in the middle of the deque invalidates all the iterators and references to elements of the deque. An insertion at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque.

(...)


Solution

  • Calling any two methods on an object of a standard class concurrently is unsafe, unless both are const, or unless otherwise specified (e.g. as is the case for std::mutex::lock()). This is explored in more detail here

    So, using emplace_back and operator[] concurrently is not safe. However, you may safely use a previously obtained reference to a deque element concurrently with a call to emplace_back/push_back due to the reference/pointer validity rules you quoted, e.g.:

    int main()
    {
        std::deque<int> d;
        d.push_back(5);
        auto &first = d[0];
        auto task = std::async(std::launch::async, [&] { first=3; });
        d.push_back(7);
        task.wait();
        for ( auto i : d )
            std::cout << i << '\n';
    }
    

    This will safely output 3 and 7. Note that the reference first is created before launching the asynchronous task.