【问题标题】:Thread-safety of writing a std::vector vs plain array编写 std::vector 与普通数组的线程安全
【发布时间】:2012-12-19 15:27:57
【问题描述】:

我读过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);
}
  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。

【问题讨论】:

  • 我不确定是否可以将其作为答案,但我很确定写入 std::vector 的不同元素是线程安全的。
  • 这两个sn-ps同样是线程安全的。
  • “但这在实践中意味着什么?” :这意味着如果任何/任一操作与并发写入同时发生,则容器必须专门为写入和读取而锁定。您可以让所有读者敲打您想要的容器,但是一旦引入写入 potential,所有赌注都关闭,您必须锁定 all 访问(不是只是其他作家)。顺便说一句,单写多读锁适用于此。

标签: c++ stl thread-safety openmp


【解决方案1】:

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

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

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

【讨论】:

  • 我写了一个小测试函数来看看openmp是否正常运行。我使用vector&lt;bool&gt;,将其大小调整为使用的线程数(初始化为false),并将所有值同时设置为true。如果每个值都为真,则函数返回真以表明所有线程都正确生成。 vector&lt;bool&gt; 再次来袭。幸运的是,我最近实际上阅读了标准的相关部分,所以我能够弄清楚,但如果有人偶然发现这一点,他们需要确保避免同时写入 vector&lt;bool&gt;。
  • 这样,从每个对应的单独线程 push_back 到 std::vector&lt;vector&lt;type&gt;&gt; 下的单个向量应该是安全的吗?
【解决方案2】:

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

尽管有 (17.6.5.9) 的规定,当同时修改同一序列中不同元素中包含的对象的内容时,需要实现以避免数据争用,vector 除外。

[ 注意:对于大小大于一的向量 x,x[1] = 5 和 *x.begin() = 10 可以同时执行而不会发生数据争用,但 x[0] = 5 和 *x.begin() = 10 同时执行可能会导致数据竞争。作为一般规则的一个例外,对于 vector 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[]。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 赞成为 0。这可能被认为不正确,但在删除的编辑之前发布:#pragma omp atomic update,它确实阻止了并发写入并使此答案在原始帖子的上下文中正确
    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2012-04-18
      • 2012-03-07
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 2021-05-26
      • 2011-09-16
      • 2014-11-29
      相关资源
      最近更新 更多