我读过on Stackoverflow 没有一个STL容器是编写的线程安全的。但这在实践中意味着什么?这是否意味着我应该将可写数据存储在普通数组中?
我希望对std::vector::push_back(element)
的并发调用可能导致数据结构不一致,因为它可能需要调整向量的大小。但是这样的情况呢,不涉及调整大小:
1)使用数组:
int data[n];
// initialize values here...
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
data[i] += func(i);
}
2)使用`std :: vector``:
std::vector<int> data;
data.resize(n);
// initialize values here...
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
data[i] += func(i);
}
第一个实现是否真的比第二个更好a)在线程安全方面和b)在性能方面?我更喜欢使用std :: vector,因为我对C风格的数组不太满意。
编辑:我删除了保护写入的#pragma omp atomic update
。
答案 0 :(得分:23)
这两个人同样安全。如果没有从多个线程访问任何元素就可以了。你的并行循环只访问每个元素一次,因此只能访问一个线程。
标准中有空间,容器的成员函数是非线程安全的。在这种情况下,您使用vector<int>::operator[]
,因此您需要明确保证该成员的线程安全性,这似乎是合理的,因为即使在非const向量上调用它也不会修改向量本身。所以我怀疑在这种情况下存在问题,但我没有寻找保证[编辑:rici发现它]。即使它可能不安全,您也可以在循环之前执行int *dataptr = &data.front()
,然后将dataptr
而不是data
编入索引。
顺便说一下,这段代码不保证vector<bool>
的安全,因为它是一个特殊情况,多个元素在一个对象中共存。对于bool
数组来说是安全的,因为它的不同元素是不同的“内存位置”(C ++ 11中的1.7)。
答案 1 :(得分:18)
对于指定数据争用规则的c ++ 11,描述了容器的线程安全性。该标准的相关部分是§23.2.2,第2段:
尽管如此(17.6.5.9),除了vector&lt; bool&gt;之外的同一序列中不同元素中包含对象的内容被同时修改时,需要实现以避免数据争用。
[注意:对于矢量&lt; int&gt;大小大于1的x,x [1] = 5且* x.begin()= 10可以在没有数据争用的情况下同时执行,但x [0] = 5且* x.begin()= 10并发执行可能会导致数据竞争。作为一般规则的例外,对于向量&lt; bool&gt; y,y [0] = true可以与y [1] =真竞赛。 - 后注]
上面提到的§17.6.5.9基本上禁止任何标准库接口的任何并发修改,除非特别允许,因此我引用的部分确切地告诉您允许的内容(包括您的使用)。
由于Steve Jessop提出了这个问题,§23.2.2的第1段明确允许在序列容器中同时使用[]
:
为避免数据争用(17.6.5.9),实现应将以下函数视为const:begin,end,rbegin,rend,front,back,data,find,lower_bound,upper_bound,equal_range,at和,除了在关联或无序的关联容器中,operator []。
答案 2 :(得分:2)
它的主要意思是,如果你有多个线程访问向量,你不能依赖C ++来防止多个并发写入破坏数据结构。所以你需要使用某种防守。另一方面,如果你的程序没有使用多个线程,就像你的例子似乎没有,你就完全没问题了。
答案 3 :(得分:1)
在这种情况下,您应该使用必要数量的值构建矢量?一切都会好起来的。
std::vector<int> data(n, 0);
resize()
效果很好。表现将是平等的。
多线程访问不会破坏向量的原因是:您的数据位于其位置,不会从那里移动。 OMP线程一次不会访问同一个元素。