Ist es parallel sicher, concurrency :: concurrent_vector :: push_back aufzurufen, während über diesen concurrent_vector in einem anderen Thread iteriert wird?

9

push_back , begin , end werden in https://docs.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back als gleichzeitig sicher beschrieben

Der folgende Code bestätigt jedoch. Wahrscheinlich, weil das Element hinzugefügt, aber noch nicht initialisiert wurde.

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;
}
Pidgun
quelle

Antworten:

2

Laut den Dokumenten sollte es sicher sein, an eine concurrency::concurrent_vectorWeile anzuhängen, die darüber iteriert, da die Elemente nicht tatsächlich zusammenhängend im Speicher gespeichert sind wie std::vector:

Ein concurrent_vectorObjekt verschiebt seine Elemente nicht, wenn Sie es anhängen oder seine Größe ändern. Dadurch können vorhandene Zeiger und Iteratoren bei gleichzeitigen Vorgängen gültig bleiben.

push_backWenn ich mir jedoch die tatsächliche Implementierung von VS2017 anschaue, sehe ich Folgendes, was ich nicht für threadsicher halte:

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);
}

Ich muss _Internal_push_backhier darüber spekulieren , aber ich würde wetten, dass es Rohspeicher zum Speichern des Elements zuweist (und das letzte Element auf diesen neuen Knoten zeigt), damit die nächste Zeile die neue Position verwenden kann. Ich würde mir vorstellen, dass dies _Internal_push_backintern threadsicher ist, aber ich sehe keine Synchronisation, bevor die Einlagerung neu ist. Das heißt, Folgendes ist möglich:

  • Speicher wird erhalten und der Knoten ist "vorhanden" (noch ist die Einlagerung neu nicht geschehen)
  • Der Loop-Thread trifft auf diesen Knoten und stellt fest memcmp, dass er nicht gleich ist
  • Einlagerung neu passiert.

Hier gibt es definitiv eine Rennbedingung. Ich kann das Problem spontan reproduzieren, umso mehr Threads verwende ich.

Ich empfehle Ihnen, ein Ticket mit Microsoft-Support für dieses Ticket zu öffnen.

AndyG
quelle