Чи безпечно для одночасного виклику concurrency :: concurrent_vector :: push_back під час ітерації над цим concurrent_vector в іншому потоці?


9

push_back , починає , кінець описується як паралельний сейфі в https://docs.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back

Однак наведений нижче код стверджує. Можливо, тому, що елемент доданий, але ще не ініціалізований.

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

Відповіді:


2

Згідно з документами , слід дотримуватися певного concurrency::concurrent_vectorчасу, повторюючи його, оскільки елементи насправді не зберігаються постійно в пам'яті, як std::vector:

concurrent_vectorОб'єкт не переміщався його елементів , коли ви додаєте до нього або змінити його розмір. Це дає змогу існуючим вказівникам та ітераторам залишатися дійсними під час одночасних операцій.

Однак, дивлячись на реальну реалізацію програми push_backVS2017, я бачу таке, що, на мою думку, не є безпечним для потоків:

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

Я маю розмірковувати _Internal_push_backтут, але я ставлю ставку, що він виділяє необмежену пам'ять для зберігання елемента (і вказує останній елемент на цей новий вузол), щоб наступний рядок міг використовувати заміщення нового. Я думаю, що _Internal_push_backце внутрішньо потоково, проте я не бачу жодної синхронізації до появи нового. Значить можливе наступне:

  • Отримано пам'ять, а вузол "присутній" (поки що нове розміщення не відбулося)
  • петлевий потік стикається з цим вузлом і виконує memcmpвиявлення того, що вони не рівні
  • emplacement нове відбувається.

Тут безумовно перегони. Я можу спонтанно відтворити проблему, тим більше, чим більше ниток використовую.

Я рекомендую вам відкрити квиток із підтримкою Microsoft на цей.



1
MSFT оновила документацію github.com/MicrosoftDocs/cpp-docs/commit/…
pidgun
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.