繁体   English   中英

C ++是否保证从两个线程访问数组的相邻元素是安全的

[英]Does C++ guarantees it is safe to access adjacent elements of an array from two threads

就C ++标准而言(C ++ 11及更高版本,我猜,因为在不考虑线程之前),并发写入数组的不同的 ,可能相邻的元素是否安全?

例如:

#include <iostream>
#include <thread>

int array[10];

void func(int i) {
   array[i] = 42;
}

int main() 
{
   for(int i = 0; i < 10; ++i) {
      // spawn func(i) on a separate thread
      // (e.g. with std::async, let me skip the details)
   }
   // join

   for(int i = 0; i < 10; ++i) {
      std::cout << array[i] << std::endl; // prints 42?
   }

   return 0;
}

在这种情况下,语言是否保证数组的不同元素的写入不会导致竞争条件? 它是否适用于任何类型,或者是否有任何安全要求?

数据竞争仅发生在相同的内存位置,即只有当&x == &y才能在两个glvalues xy上进行数据竞争。

[intro.races] / 2

如果其中一个修改内存位置而另一个读取或修改相同的内存位置,则两个表达式评估会发生冲突。

[intro.races] / 21

如果一个程序的执行包含两个可能同时发生冲突的动作,那么该程序的执行包含数据竞争[...]

其余部分不适用于此处。 所以你的阵列没有数据竞争。

是。

来自https://en.cppreference.com/w/cpp/language/memory_model

当表达式的评估写入内存位置而另一个评估读取或修改相同的内存位置时,表达式会发生冲突。 具有两个相互冲突的评估的程序具有数据竞争,除非[...]

然后:

内存位置

  • 标量类型的对象(算术类型,指针类型,枚举类型或std :: nullptr_t)
  • 或非零长度的最大连续的位域序列

因此,如果数组的元素存储在不同的内存位置,则不会产生冲突的评估。

一个数组是:

表格T a[N]; ,将a声明为一个数组对象,由N个连续分配的T类型对象组成。

由于两个不同的对象不能具有相同的地址,因此它们及其组成者不能具有相同的存储位置。 这保证了对早期要求的满意度。

此外,对象可以包含多个内存位置,因此您甚至可以让两个线程对同一对象的不同成员进行操作!

请注意,为了使您的示例正确,连接也必须正确编写,但它与数组的相邻元素无关,而是在同一个元素上运行,所以我想它超出了问题的范围。


个人说明:顺便说一句。 如果不能保证这一点,那么如果不在标准库中进行无用的并行计算,则会严重限制。

是的,但“确定”并不意味着这样做很聪明。

有几个问题需要考虑,也许最重要的一个是CPU缓存。 在例如x86上,高速缓存行是64字节长,因此每个线程应该例如在与高速缓存行长度匹配的阵列块上工作以避免例如错误共享。

这是一个例子: 假共享SO问题/答案

并行访问不同线程上的连续元素是安全的 ,但如果频繁发生, 则可能导致代码中出现性能问题。 这是由于现代CPU上并行性的基本限制。

对于大多数程序来说,内存访问是一个主要的瓶颈。 必须仔细编写代码的性能关键部分,以避免过多的缓存未命中。 缓存有多个级别,每个级别比前一个级别更快。 但是,当数据不在缓存中时,或者数据可能已被另一个CPU更改时,必须将其重新加载到缓存中。

CPU无法跟踪每个字节的状态,因此它会跟踪称为高速缓存行的字节块。 如果高速缓存行中的任何字节由单独的CPU更改,则必须重新加载以确保同步。

在不同线程上访问单独的字节只会导致重新加载,如果它们位于同一缓存行中。 并且因为高速缓存行是连续的,所以从不同的线程访问连续的元素通常会导致必须将存储器重新加载到高速缓存中。 这称为虚假共享,如果性能受到关注,应在并行代码中避免使用。

话虽这么说,如果很少发生它可能很好,你应该在优化它之前对你的代码进行基准测试和测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM