11

push_backbeginend在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;
}
4

1 回答 1

3

根据文档,应该安全地追加到一段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这里推测,但我敢打赌它会分配原始内存来存储项目(并将最后一个元素指向这个新节点),以便下一行可以使用新的 emplacement。我想这_Internal_push_back是内部线程安全的,但是我没有看到在新位置之前发生任何同步。意味着以下是可能的:

  • 获得内存并且节点“存在”(但没有发生新的安置)
  • 循环线程遇到此节点并执行memcmp以发现它们不相等
  • 新的安置发生。

这里肯定有竞争条件。我可以自发地重现问题,而且我使用的线程越多。

我建议您在此问题上通过 Microsoft 支持打开一张票。

于 2019-11-15T13:12:47.130 回答