Search code examples
c++multithreadingthread-safetystdvector

Pushing to a vector and iterating on it from different threads


Is such a piece of code safe?

vector<int> v;

void thread_1()
{
    v.push_back(100);
}

void thread_2()
{
    for (int i : v)
    {
        // some actions
    }
}

Does for (int i : v) compile into something like:

for ( ; __begin != __end; ++__begin)
{
    int i = *__begin;
}

Is it possible that push_back in first thread will make data reallocation (when size == capacity), remove old data and *__begin in another thread will dereference freed iterator? And a runtime crash will occur

If so, how should I synchronize the threads? Something like:

void thread_1()
{
    mtx.lock();
    v.push_back(100);
    mtx.unlock();
}

void thread_2()
{
    mtx.lock();
    for (int i : v)
    {
        // some actions
    }
    mtx.unlock();
}

?


Solution

  • Simply put, On some architectures, Fundamental types are inherently atomic, while on others they are not.

    On those architectures, writing and reading from and to vector<int> v is thread safe as long as no reallocation occurs and ints are properly aligned; but it depends on various factors.

    BUT:

    • you may want to avoid writing architecture-specific code (unless you want your code to basically run only on your own computer)

    • You have no mechanism in your code to prevent reallocation (which may invalidate iterators held by other threads), and since you also have no mechanism to synchronize the threads in your code, such reallocations can easily occur.

    • Considering your design, if you have a std::vector of classes/structs instead of Fundamental Types, you will also risk race conditions and/or UB even in simple concurrent read/writes since one thread can see the vector's element in a broken state (i.e. thread2 can see element#x in vector while it is being changed{push_back'd} by thread1)

    in order to ensure thread safety, you have many options:

    1. Prevent modifications to the queue entirely while its being read/written to - basically what you are doing in you own solution- using a global mutual exclusion mechanism
    2. Prevent modifications by other threads for the element currently being manipulated using fine-grained mutual exclusion (could get tricky for linked data structures)
    3. Use thread-safe data structure which have built-in mechanisms to ensure that a single element cannot be accessed by multiple threads
    4. ...