7

来自以下文档的摘录emplace_back()

  • 迭代器有效性

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

  • 数据竞赛

容器被修改。

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

以及来自以下文档的摘录operator[]()

  • 数据竞赛

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

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

那么,鉴于双端队列的某些实例至少有一个元素,通过容器访问它operator[]()并同时调用emplace_back()它确实是线程安全的吗?

我倾向于说是,但无法确定emplace_back()'s 文档中的“访问”是否包括使用operator[]()as:

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

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

其中两个函数被同时调用,或者“访问”仅适用于已获取某些引用或指针的元素:

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

auto * ptr = & q[ 0 ]

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

编辑:为了进一步参考,以下是 C++ 14 标准(实际上是2014 年 11 月的工作草案,N4296)关于deque引用和迭代器有效性的插入说明:

  • 23.3.3.4 双端队列修饰符

(...)

  1. 效果:双端队列中间的插入使所有迭代器和对双端队列元素的引用无效。在双端队列的任何一端插入都会使双端队列的所有迭代器无效,但不会影响对双端队列元素的引用的有效性。

(...)

4

2 回答 2

6

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

因此,同时使用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是在启动异步任务之前创建的。

于 2016-12-06T18:14:08.803 回答
3

编辑说明:这个答案的结论是不正确的,[]并且emplace_back可以安全地同时使用。Arne 的回答是正确的。由于评论很有用,因此将其保留在此处而不是删除。

Edit2:嗯,从技术上讲,我没有得出这个结论,但它是隐含的。Arne's 是更好的答案。


尽管我并不完全信任来源,但该文档似乎在说的是,只要您不通过迭代器进行并发访问,其他值就是线程安全的。

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

容器不需要以任何方式是线程安全的。这就像a[0]在修改时访问a[1]。只要您正确访问/修改该数据(不要导致 UB),它就是安全操作。您不需要任何锁来保护,因为您没有同时使用相同的数据。

我更关心的是size。这很可能是 中的一个值,deque由 修改emplace和读取size。如果没有保护,这将导致数据竞争。文档对此只字未提,只涉及元素的访问,当然可以同时调用size.

根据这个答案,除了上述之外,标准容器对线程安全没有任何保证。换句话说,您可以同时访问/修改不同的元素,但其他任何事情都可能导致数据竞争。然而换句话说,标准容器不是线程安全的,也不提供任何并发保护。

于 2016-12-06T17:49:47.783 回答