我有一个std::deque<std::pair<CustomObj, int>>
,在并发块开始时不会改变大小。
并发块会读取deque
中的每一个CustomObj
并设置它的int
值。
我可以保证deque的大小不会改变,因此它不会重新分配内存,并且每个线程只会访问deque的一个内存块而不会访问其他线程的内存块。
同时读写会导致未定义行为吗?我应该把读写放在互斥区域里吗?
我有一个std::deque<std::pair<CustomObj, int>>
,在并发块开始时不会改变大小。
并发块会读取deque
中的每一个CustomObj
并设置它的int
值。
我可以保证deque的大小不会改变,因此它不会重新分配内存,并且每个线程只会访问deque的一个内存块而不会访问其他线程的内存块。
同时读写会导致未定义行为吗?我应该把读写放在互斥区域里吗?
令我惊讶的是,当前标准本身实际上有一个非常清晰的部分涉及到这个问题:
(C++17, 26.2.2 容器数据竞争, 2)
- 尽管20.5.5.9存在,但实现必须避免在同一容器中不同元素的包含对象的内容同时被修改时发生数据竞争(除了
vector<bool>
)。
此外,你可以放心地调用以下访问函数:
- 为了避免数据竞争(20.5.5.9),实现应将以下函数视为const:
begin、end、rbegin、rend、front、back、data、find、lower_bound、upper_bound、equal_range、at
和除关联或无序关联容器之外的operator[]
。
由于std::deque
也不属于例外情况,因此您可以安全地并发调用任何这些函数以获取不同的元素并修改它们。只要确保将对容器本身的任何修改与您并发访问和修改元素的并行区域正确隔离即可。
例如,以下操作是错误的:
std::dequeue<...> dq;
#pragma omp master
{
...
dq.emplace(...);
}
// no implicit barrier here,
// use omp barrier or change to omp single instead of master
#pragma omp for
for (... i; ...)
dq[i].second = compute(dq[i]);
deque
的大小不变且只有一个线程会写入特定元素,那么是安全的。只有在尝试修改deque
(更改其大小)或在多个线程读写deque
中的单个元素时才会出现问题。#pragma omp for
即可。 - Zulanstd::vector
的这篇答案声称 "在写入时没有并发读取"。 它没有说他是指容器还是容器的元素。 我仍然认为,像你一样,鉴于我的上下文,安全地编写和读取不同的内存位置。 另外,在我的并发块中没有 push_back
。 - quimnuss所有标准容器的规则是:
这只是适度地太强了。您可以做一些违反上述规则的小事情,而不会在标准下成为竞争条件。
std::vector<bool>
,这让我写下了这个答案,因为它太长了,不适合作为现有评论的回复。 - Zulandeque
在插入到任一端时具有引用稳定性,因此如果在代码的同步部分中可以执行auto &ref = dq[i];
,那么ref
可以与push_back()
、push_front()
等同时访问。不幸的是,迭代器会失效,所以在给定的循环中,你必须事先存储每个单独的指针或引用。 - Arne Vogel