È sicuro per la concorrenza chiamare concorrenza :: concurrent_vector :: push_back mentre si scorre su quel concurrent_vector in altri thread?


9

push_back , inizio , fine sono descritti come sicurezza simultanea in https://docs.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back

Tuttavia, il seguente codice sta affermando. Probabilmente perché l'elemento è stato aggiunto ma non ancora inizializzato.

struct MyData
   {
   explicit MyData()
      {
      memset(arr, 0xA5, sizeof arr);
      }
   std::uint8_t arr[1024];
   };

struct MyVec
   {
   concurrency::concurrent_vector<MyData> v;
   };

auto vector_pushback(MyVec &vec) -> void
   {
   vec.v.push_back(MyData{});
   }

auto vector_loop(MyVec &vec) -> void
   {
   MyData myData;
   for (auto it = vec.v.begin(); it != vec.v.end(); ++it)
      {
      auto res = memcmp(&(it->arr), &(myData.arr), sizeof myData.arr);
      assert(res == 0);
      }
   }

int main()
{
   auto vec = MyVec{};
   auto th_vec = std::vector<std::thread>{};
   for (int i = 0; i < 1000; ++i)
      {
      th_vec.emplace_back(vector_pushback, std::ref(vec));
      th_vec.emplace_back(vector_loop, std::ref(vec));
      }

   for(auto &th : th_vec)
      th.join();

    return 0;
}

Risposte:


2

Secondo i documenti , dovrebbe essere sicuro aggiungere un concurrency::concurrent_vectorpo 'di iterazione su di esso perché gli elementi non sono effettivamente archiviati contigui nella memoria come std::vector:

Un concurrent_vectoroggetto non sposta i suoi elementi quando lo aggiungi o lo ridimensioni. Ciò consente ai puntatori e agli iteratori esistenti di rimanere validi durante le operazioni simultanee.

Tuttavia, osservando l'implementazione effettiva di push_backVS2017, vedo quanto segue, che non credo sia thread-safe:

iterator push_back( _Ty &&_Item )
{
    size_type _K;
    void *_Ptr = _Internal_push_back(sizeof(_Ty), _K);
    new (_Ptr) _Ty( std::move(_Item));
    return iterator(*this, _K, _Ptr);
}

Devo speculare _Internal_push_backqui, ma scommetto che alloca memoria grezza per la memorizzazione dell'elemento (e punta l'ultimo elemento verso questo nuovo nodo) in modo che la riga successiva possa usare la postazione nuova. Immagino che _Internal_push_backsia internamente sicuro per i thread, tuttavia non vedo alcuna sincronizzazione avvenire prima della nuova postazione. Ciò significa che è possibile:

  • la memoria viene ottenuta e il nodo è "presente" (eppure la nuova postazione non è avvenuta)
  • il thread in loop incontra questo nodo ed esegue memcmpper scoprire che non sono uguali
  • postazione nuova accade.

C'è sicuramente una condizione di gara qui. Posso riprodurre spontaneamente il problema, più sono i thread che utilizzo.

Ti consiglio di aprire un ticket con il supporto Microsoft su questo.



1
Documentazione aggiornata di MSFT github.com/MicrosoftDocs/cpp-docs/commit/…
pidgun
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.