É seguro para simultaneidade chamar concurrency :: concurrent_vector :: push_back enquanto itera sobre esse concurrent_vector em outro thread?

9

push_back , begin , end são descritos como segurança simultânea em https://docs.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back

No entanto, o código abaixo está afirmando. Provavelmente porque o elemento foi adicionado, mas ainda não foi inicializado.

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
fonte

Respostas:

2

De acordo com a documentação , deve ser seguro acrescentar um concurrency::concurrent_vectortempo a iteração, porque os elementos não são realmente armazenados contiguamente na memória, como std::vector:

Um concurrent_vectorobjeto não realoca seus elementos quando você o anexa ou redimensiona. Isso permite que ponteiros e iteradores existentes permaneçam válidos durante operações simultâneas.

No entanto, observando a implementação real do push_backVS2017, vejo o seguinte, que não acho que seja seguro para threads:

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

Eu tenho que especular _Internal_push_backaqui, mas eu apostaria que ele aloca memória bruta para armazenar o item (e aponta o último elemento para esse novo nó) para que a próxima linha possa usar a nova colocação. Eu imagino que _Internal_push_backseja internamente seguro para threads, no entanto, não vejo nenhuma sincronização acontecendo antes da nova colocação. Significando o seguinte é possível:

  • a memória é obtida e o nó está "presente" (mas a nova colocação não aconteceu)
  • o encadeamento em loop encontra esse nó e executa memcmppara descobrir que eles não são iguais
  • colocação nova acontece.

Definitivamente, há uma condição de corrida aqui. Posso reproduzir espontaneamente o problema, mais ainda mais tópicos que uso.

Eu recomendo que você abra um ticket com o suporte da Microsoft neste.

AndyG
fonte