我有一个使用很多std::map
结构的程序。现在,我想将它们与多个线程一起使用,并假定插入或删除键可能会更改整个数据结构并并行破坏它。但是当我不添加新密钥时,应该没事吧?
以下程序显示了我想做什么:
#include <omp.h>
#include <iostream>
#include <map>
int main(int const argc, char const *const *const argv) {
// Take a map and allocate the elements, but not fill them at this point.
std::map<int, int> map;
int size = 10000;
for (int i = 0; i < size; ++i) {
map[i];
}
// Go through the elements in parallel and write to them, but not create any
// new elements. Therefore there should not be any allocations and it should
// be thread-safe.
#pragma omp parallel
{
int const me = omp_get_thread_num();
#pragma omp for
for (int i = 0; i < size; ++i) {
map[i] = me;
}
}
// Now all threads access all the elements of the map, but as the map is not
// changed any more, nothing bad should happen.
#pragma omp parallel
{
int const me = omp_get_thread_num();
int self = 0;
for (int i = 0; i < size; ++i) {
if (map[i] == me) {
++self;
}
}
#pragma omp critical(cout)
std::cout << "Thread " << me << " found " << self << " entries.\n";
}
}
然后用以下代码进行编译:
$ g++ -fopenmp -O3 -Wall -Wpedantic -g -fsanitize=address -o concurrent-map concurrent-map.cpp
这似乎可以在四个线程上正常工作。如果我注释掉第一个for循环并让线程填充地图,则它会由于我的期望而导致分段错误。
当然,我无法以这种方式证明std::map
是线程安全的,但至少不能证明是负面的。我可以并行使用std::map
吗?
答案 0 :(得分:3)
我认为使用map[i]
对于所有C ++实现都不是线程安全的,即使它没有插入新元素也是如此。该标准不要求operator[]
不受关联容器的数据竞争:
C ++ 17标准草案的[container.requirement.dataraces]/1部分包含一个函数列表,即使它们不是const
也不应引起数据争用。该列表包括find
和at
,但不包括operator[]
。
因此,您需要使用find
或at
而不是operator[]
。特定的实现可能会提供更强的保证,并且可能在map[i]
不插入新元素的情况下实现,但是您需要使用编译器/标准库文档进行检查。
除此之外,对容器的不同元素进行访问(甚至进行修改)总是可以的(vector<bool>
除外),请参阅标准的下一段。
答案 1 :(得分:2)
编辑:
由于无法保证operator[]
不会修改结构,因此无法保证它可以正常工作。最好使用at()
或find()
。
据我了解C ++标准和OpenMP文档-这是安全的。首先,只要您不进行修改迭代器的操作,并行修改就可以了。
第二个问题是写在不同线程中的数据在其他线程中是否可见。幸运的是,OpenMP有相当不错的文档,其中指出内存同步是隐式发生的:
在每个隐式任务的任务区域的退出处;