22

在 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);
}
  1. 使用`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保护写入。

4

4 回答 4

27

两者同样安全。如果没有从多个线程访问元素,你就可以了。您的并行循环将只访问每个元素一次,因此只能从一个线程访问。

标准中有空间让容器的成员函数成为非线程安全的。在这种情况下,您使用vector<int>::operator[],因此您需要明确保证该成员的线程安全,这似乎是合理的,因为即使在非常量向量上调用它也不会修改向量本身。所以我怀疑在这种情况下是否存在问题,但我没有寻找保证[编辑:rici 找到它]。即使它可能不安全,您也可以int *dataptr = &data.front()在循环之前执行然后索引关闭dataptr而不是data.

顺便说一句,这段代码不能保证安全vector<bool>,因为它是多个元素共存于一个对象中的一种特殊情况。数组是安全的bool,因为其中的不同元素是不同的“内存位置”(C++11 中的 1.7)。

于 2012-12-19T15:33:21.617 回答
18

对于指定数据竞争规则的 c++11,描述了容器的线程安全性。该标准的相关部分是第 23.2.2 节第 2 段:

尽管有(17.6.5.9),当同时修改同一序列中不同元素中包含的对象的内容时,需要实现来避免数据竞争,除了vector<bool>。

[注意:对于大小大于一的向量<int> x,x[1] = 5 和 *x.begin() = 10 可以在没有数据竞争的情况下同时执行,但是 x[0] = 5 和 * x.begin() = 10 同时执行可能会导致数据竞争。作为一般规则的一个例外,对于 vector<bool> y,y[0] = true 可能与 y[1] = true 竞争。——尾注]

除非特别允许,否则提到的 § 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[]。

于 2012-12-19T15:39:24.843 回答
3

它的主要含义是,如果您有多个线程访问该向量,则不能依赖 C++ 来防止您通过多个并发写入来破坏数据结构。所以你需要使用某种保护。另一方面,如果您的程序不使用多个线程,而您的示例似乎没有,那么您就很好了。

于 2012-12-19T15:30:54.330 回答
1

在这种情况下,您应该只用必要数量的值构建您的向量吗?一切都会好起来的。

std::vector<int> data(n, 0);

resize()效果很好。性能将是相同的。多线程访问不会破坏向量的原因是:您的数据位于它的位置并且不会从那里移动。OMP 线程一次不会访问同一个元素。

于 2012-12-19T15:32:04.463 回答