Czy wywoływanie współbieżności :: concurrent_vector :: push_back jest bezpieczne podczas iteracji po tym współbieżnym wektorze w innym wątku?

9

push_back , zaczynają , koniec opisane są jako równoczesne sejf w https://docs.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back

Jednak poniższy kod jest twierdzący. Prawdopodobnie dlatego, że element został dodany, ale jeszcze nie zainicjowany.

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
źródło

Odpowiedzi:

2

Według dokumentów powinno być bezpiecznie dołączać się do niej concurrency::concurrent_vectorpodczas iteracji, ponieważ elementy nie są tak naprawdę przechowywane w pamięci w sposób ciągły, jak std::vector:

concurrent_vectorObiekt nie przenieść jego elementy, gdy dołączy do niego lub zmienić jego rozmiar. Umożliwia to zachowanie istniejących wskaźników i iteratorów podczas jednoczesnych operacji.

Jednak patrząc na rzeczywistą implementację push_backVS2017, widzę następujące, które moim zdaniem nie są bezpieczne dla wątków:

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

Muszę spekulować _Internal_push_backtutaj, ale postawiłbym, że przydziela surową pamięć do przechowywania elementu (i wskazuje ostatni element w kierunku tego nowego węzła), aby w następnym wierszu można było użyć nowego położenia. Wyobrażam sobie, że _Internal_push_backjest wewnętrznie bezpieczny dla wątków, jednak nie widzę żadnej synchronizacji przed nowym umieszczeniem. Oznacza to, że możliwe są:

  • pamięć jest pobierana, a węzeł jest „obecny” (jeszcze nie nastąpiło umieszczenie nowego)
  • zapętlony wątek napotyka ten węzeł i próbuje memcmpwykryć, że nie są one równe
  • nowe miejsce pracy się dzieje.

Zdecydowanie jest to warunek wyścigu. Mogę spontanicznie odtworzyć problem. Im więcej wątków używam.

Zalecam otwarcie biletu ze wsparciem Microsoft w tym przypadku.

AndyG
źródło