从std :: deque线程安全地并发调用emplace_back()和operator []()吗?

时间:2016-12-06 17:11:00

标签: c++ multithreading thread-safety stddeque

摘录emplace_back()的文档:

  
      
  • 迭代器有效期
  •   
     

与此容器相关的所有迭代器都是无效的,但指针和引用仍然有效,指的是它们在调用之前引用的相同元素。

     
      
  • 数据竞赛
  •   
     

容器已修改。

     

调用不会访问包含的元素:同时访问或修改它们是安全的(尽管请参阅上面的迭代器有效性)。

摘自operator[]()的文档:

  
      
  • 数据竞赛
  •   
     

访问容器(const和非const版本都不会修改容器)。

     

可能会访问或修改元素 n 。同时访问或修改其他元素是安全的。

因此,假设 deque 的某个实例至少有一个元素,通过operator[]()访问它并同时调用容器上的emplace_back()确实是线程安全的吗?< / p>

我倾向于说它是,但无法决定emplace_back()的文档中“访问”是否包含operator[]()的使用,如下所示:

int access( std::deque< int > & q )
{
    return q[ 0 ];
}

void emplace( std::deque< int > & q , int i )
{
    q.emplace_back( i );
}

其中两个函数同时调用,或者“access”仅适用于已经采用某些引用或指针的元素:

std::deque< int > q { 1 };

auto * ptr = & q[ 0 ]

std::thread t1 ( [ ptr  ]{ * ref = 0; } );
std::thread t2 ( [ & q ]{ q.emplace_back( 2 ); } );

编辑:为了进一步参考,这里有关于引用和迭代器有效性的deque中关于插入的C ++ 14标准(实际上是November 2014 Working Draft, N4296)的说明: / p>

  
      
  • 23.3.3.4 deque modifiers
  •   
     

(...)

     
      
  1. 效果:在双端队列中间的插入使所有迭代器和对双端队列元素的引用无效。在deque两端的插入使deque的所有迭代器无效,但对deque元素的引用的有效性没有影响。
  2.         

    (...)

2 个答案:

答案 0 :(得分:4)

同时在标准类的对象上调用任意两个方法是不安全的,除非两者都是const,或者除非另有说明(例如std::mutex::lock()的情况)。我们将更详细地探讨这一点here

因此,同时使用emplace_backoperator[] 是安全的。但是,由于您引用的引用/指针有效性规则,您可以安全地使用之前获得的对deque元素的引用以及对emplace_back / push_back的调用,例如:

int main()
{
    std::deque<int> d;
    d.push_back(5);
    auto &first = d[0];
    auto task = std::async(std::launch::async, [&] { first=3; });
    d.push_back(7);
    task.wait();
    for ( auto i : d )
        std::cout << i << '\n';
}

这将安全输出3和7.请注意,在启动异步任务之前会创建引用first

答案 1 :(得分:2)

编辑注释:此答案的结论不正确[]emplace_back可以同时使用。 Arne的回答是正确的。由于评论有用,请将此留在这里而不是删除。

编辑2:嗯,从技术上讲,我没有得出这个结论,但它有点暗示。 Arne的答案更好。

本文档似乎说的是,尽管我并不完全信任源代码,但只要您不执行此操作,同时访问其他值是线程安全的通过迭代器。

这种情况的原因是emplace_back不会以任何方式触及其他值。如果容量太低而无法添加其他元素,则会分配新页面。这不会影响其他元素。因此,通过其他线程使用这些值是安全的。它不会导致数据竞争,因为没有访问/修改相同的数据。

对于这种情况,容器不需要以任何方式保证线程安全。这与修改a[0]时访问a[1]类似。只要您正确访问/修改数据(不会导致UB),它就是一个安全的操作。您不需要任何锁保护,因为您不会同时使用相同的数据。

我更关心的是size。这可能是由deque修改并由emplace读取的size中的值。如果没有保护,这将导致数据竞争。文档没有说明这一点,只涉及元素的访问,当然可以同时调用size

根据this answer,除上述情况外,标准容器对螺纹安全性没有任何保证。换句话说,您可以同时访问/修改不同的元素,但其他任何因素都可能导致数据竞争。换句话说,标准容器不是线程安全的,并且不提供任何并发保护。