从 std::d eque 线程对 emplace_back() 和运算符 []() 的并发调用是否安全?
Are concurrent calls to emplace_back() and operator[]() from std::deque thread safe?
emplace_back()
文档摘录:
- 迭代器有效性
与此容器相关的所有迭代器都失效,但指针和引用仍然有效,引用它们在调用之前引用的相同元素。
- 数据竞赛
容器已修改。
调用不会访问任何包含的元素:并发访问或修改它们是安全的(尽管请参阅上面的迭代器有效性)。
以及operator[]()
文档的摘录:
- 数据竞赛
将访问容器(const 和非 const 版本都不会修改容器)。
元素n可能被访问或修改。同时访问或修改其他元素是安全的。
那么,鉴于deque的某个实例至少有一个元素,通过operator[]()
访问它并同时在容器上调用emplace_back()
确实是线程安全的吗?
我倾向于说是这样,但无法确定emplace_back()
文档中的"访问"是否包括使用operator[]()
,如下所示:
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 德克修饰符
(...
- 效果:在双端的中间插入会使所有迭代器和对双端元素的引用无效。在双端的任一端插入会使对双端的所有迭代器无效,但对双端元素的引用的有效性没有影响。
(...
同时调用标准类的对象上的任意两个方法是不安全的,除非两者都const
,或者除非另有指定(例如,std::mutex::lock()
的情况)。这里更详细地探讨了这一点
因此,同时使用emplace_back
和operator[]
是不安全的。但是,由于您引用的引用/指针有效性规则,您可以安全地使用先前获得的对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
是在启动异步任务之前创建的。
编辑注意:这个答案的结论是不正确的,即[]
和emplace_back
可以同时使用。 阿恩的回答是正确的。 由于评论有用,将其留在这里而不是删除。
编辑2:嗯,从技术上讲,我没有得出这个结论,但它有点暗示。 阿恩的答案更好。
尽管我不完全信任源代码,但本文档似乎在说,只要您不通过迭代器执行此操作,并发访问其他值就是线程安全的。
这种情况的原因是emplace_back
不会以任何方式触及其他值。 如果容量太低而无法添加另一个元素,则会分配新页面。 这不会影响其他元素。 因此,通过其他线程使用这些值是安全的。 它永远不会导致数据竞争,因为相同的数据没有被访问/修改。
容器不需要以任何方式是线程安全的,这种情况就是这种情况。 这就像在修改a[0]
时访问a[1]
。 只要您正确访问/修改该数据(不会导致 UB),它就是安全的操作。 您不需要任何锁来保护,因为您不会同时使用相同的数据。
我更关心的是size
. 这很可能是deque
中的一个值,由emplace
修改并由size
读取。 如果没有保护,这将导致数据争用。 文档对此没有任何说明,只涉及元素的访问,这当然可以与调用size
同时进行。
根据这个答案,除上述情况外,标准容器对线程安全性没有任何保证。 换句话说,您可以同时访问/修改不同的元素,但其他任何事情都可能导致数据竞争。 但换句话说,标准容器不是线程安全的,并且不提供任何并发保护。
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- 为什么使用 "this" 指针调用派生成员函数?
- 函数调用中参数的顺序重要吗
- OpenGL - 在抛出"__gnu_cxx::recursive_init_error"实例后终止调用?
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 由并发无序映射查找线程调用的函数是否安全?
- qt并发没有用于调用"运行"的匹配函数
- Qt 并发:从另一个类调用成员函数
- 从 std::d eque 线程对 emplace_back() 和运算符 []() 的并发调用是否安全?
- 为什么我可以选择*不*调用并发::代理::d一个内部运行
- 运行并发的CUDA内核,从C 包装器功能调用
- Qt:QFuture/Qt并发超时函数调用
- 解释此并发中的"="符号参数::任务调用
- 并发std::call_once调用
- 对C++对象的并发远程过程调用
- 如何在c++中处理阻塞调用的并发任务
- 线程安全并发调用外部命令在c++中
- Linux 中的并发系统调用