从 std::deque 线程对 emplace_back() 和 operator[]() 的并发调用是否安全?
Are concurrent calls to emplace_back() and operator[]() from std::deque thread safe?
来自 emplace_back()
的文档摘录:
- Iterator validity
All iterators related to this container are invalidated, but pointers and references remain valid, referring to the same elements they were referring to before the call.
- Data races
The container is modified.
No contained elements are accessed by the call: concurrently accessing or modifying them is safe (although see iterator validity above).
以及来自 operator[]()
的文档摘录:
- Data races
The container is accessed (neither the const nor the non-const versions modify the container).
Element n is potentially accessed or modified. Concurrently accessing or modifying other elements is safe.
因此,鉴于 deque 的某个实例至少有一个元素,通过 operator[]()
访问它并在容器上同时调用 emplace_back()
是确实是线程安全的?
我倾向于说是,但无法确定 emplace_back()
文档中的 "accessing" 是否包含 operator[]()
的使用,如:
int access( std::deque< int > & q )
{
return q[ 0 ];
}
void emplace( std::deque< int > & q , int i )
{
q.emplace_back( i );
}
同时调用两个函数,或者 "accessing" 仅适用于已采用某些引用或指针的元素:
std::deque< int > q { 1 };
auto * ptr = & q[ 0 ]
std::thread t1 ( [ ptr ]{ * ref = 0; } );
std::thread t2 ( [ & q ]{ q.emplace_back( 2 ); } );
edit:为了进一步参考,这里是 C++ 14 标准(实际上是 November 2014 Working Draft, N4296)关于在 deque
中关于引用的插入的说明和迭代器有效性:
- 23.3.3.4 deque modifiers
(...)
- Effects: An insertion in the middle of the deque invalidates all the iterators and references to elements of the deque. An insertion at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque.
(...)
编辑说明:这个答案的结论是 []
和 emplace_back
可以安全地同时使用是不正确的。 Arne 的回答是正确的。由于评论有用,将其保留在这里而不是删除。
Edit2:嗯,从技术上讲,我没有做出那个结论,但它有点暗示。 Arne 的答案更好。
虽然我并不完全相信来源,但该文档似乎在说的是,只要您不这样做,并发访问 other 值是线程安全的它通过一个迭代器。
出现这种情况的原因是 emplace_back
不会以任何方式触及其他值。如果容量太低而无法添加另一个元素,则会分配一个新页面。这不会影响其他元素。因此通过其他线程使用这些值是安全的。它永远不会导致数据竞争,因为相同的数据不是 accessed/modified。
容器不需要以任何方式是线程安全的。这就像在修改 a[1]
的同时访问 a[0]
。只要您 access/modify 数据正确(不会导致 UB),它就是一个安全的操作。您不需要任何锁来保护,因为您没有同时使用相同的数据。
我比较关心的是size
。这很可能是 deque
中的一个值,由 emplace
修改并由 size
读取。如果没有保护,这将导致数据竞争。文档对此只字未提,只涉及元素的访问,这当然可以与对 size
.
的调用同时进行
根据this answer,除上述内容外,标准容器对线程安全没有任何保证。换句话说,您可以同时使用 access/modify 不同的 元素,但任何其他元素都可能导致数据竞争。然而换句话说,标准容器不是线程安全的,也不提供任何并发保护。
在标准 class 的对象上同时调用任何两个方法是不安全的,除非两者都是 const
,或者除非另有说明(例如 std::mutex::lock()
的情况) .对此进行了更详细的探讨 here
因此,同时使用 emplace_back
和 operator[]
不 是安全的。但是,由于您引用的 reference/pointer 有效性规则,您可以安全地使用先前获得的对 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()
的文档摘录:
- Iterator validity
All iterators related to this container are invalidated, but pointers and references remain valid, referring to the same elements they were referring to before the call.
- Data races
The container is modified.
No contained elements are accessed by the call: concurrently accessing or modifying them is safe (although see iterator validity above).
以及来自 operator[]()
的文档摘录:
- Data races
The container is accessed (neither the const nor the non-const versions modify the container).
Element n is potentially accessed or modified. Concurrently accessing or modifying other elements is safe.
因此,鉴于 deque 的某个实例至少有一个元素,通过 operator[]()
访问它并在容器上同时调用 emplace_back()
是确实是线程安全的?
我倾向于说是,但无法确定 emplace_back()
文档中的 "accessing" 是否包含 operator[]()
的使用,如:
int access( std::deque< int > & q )
{
return q[ 0 ];
}
void emplace( std::deque< int > & q , int i )
{
q.emplace_back( i );
}
同时调用两个函数,或者 "accessing" 仅适用于已采用某些引用或指针的元素:
std::deque< int > q { 1 };
auto * ptr = & q[ 0 ]
std::thread t1 ( [ ptr ]{ * ref = 0; } );
std::thread t2 ( [ & q ]{ q.emplace_back( 2 ); } );
edit:为了进一步参考,这里是 C++ 14 标准(实际上是 November 2014 Working Draft, N4296)关于在 deque
中关于引用的插入的说明和迭代器有效性:
- 23.3.3.4 deque modifiers
(...)
- Effects: An insertion in the middle of the deque invalidates all the iterators and references to elements of the deque. An insertion at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque.
(...)
编辑说明:这个答案的结论是 []
和 emplace_back
可以安全地同时使用是不正确的。 Arne 的回答是正确的。由于评论有用,将其保留在这里而不是删除。
Edit2:嗯,从技术上讲,我没有做出那个结论,但它有点暗示。 Arne 的答案更好。
虽然我并不完全相信来源,但该文档似乎在说的是,只要您不这样做,并发访问 other 值是线程安全的它通过一个迭代器。
出现这种情况的原因是 emplace_back
不会以任何方式触及其他值。如果容量太低而无法添加另一个元素,则会分配一个新页面。这不会影响其他元素。因此通过其他线程使用这些值是安全的。它永远不会导致数据竞争,因为相同的数据不是 accessed/modified。
容器不需要以任何方式是线程安全的。这就像在修改 a[1]
的同时访问 a[0]
。只要您 access/modify 数据正确(不会导致 UB),它就是一个安全的操作。您不需要任何锁来保护,因为您没有同时使用相同的数据。
我比较关心的是size
。这很可能是 deque
中的一个值,由 emplace
修改并由 size
读取。如果没有保护,这将导致数据竞争。文档对此只字未提,只涉及元素的访问,这当然可以与对 size
.
根据this answer,除上述内容外,标准容器对线程安全没有任何保证。换句话说,您可以同时使用 access/modify 不同的 元素,但任何其他元素都可能导致数据竞争。然而换句话说,标准容器不是线程安全的,也不提供任何并发保护。
在标准 class 的对象上同时调用任何两个方法是不安全的,除非两者都是 const
,或者除非另有说明(例如 std::mutex::lock()
的情况) .对此进行了更详细的探讨 here
因此,同时使用 emplace_back
和 operator[]
不 是安全的。但是,由于您引用的 reference/pointer 有效性规则,您可以安全地使用先前获得的对 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
是在启动异步任务之前创建的。