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

时间:2019-04-12 08:44:25

标签: c++ multithreading c++11 thread-safety language-lawyer

就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;
}

在这种情况下,语言是否保证数组的不同元素的写入不会引起竞争条件?并且可以保证任何类型的安全性吗?

4 个答案:

答案 0 :(得分:1)

数据争用仅发生在相同的内存位置,即只有x时,两个glvalues y&x == &y上才可能存在数据争用。

[intro.races]/2

  

如果两个表达式求值之一修改一个内存位置,而另一个表达式读取或修改了相同的内存位置,则冲突。

[intro.races]/21

  

如果程序的执行包含两个潜在的并发冲突动作[...]

其余部分在这里不适用。因此,您的阵列上没有数据竞争。

答案 1 :(得分:1)

是的

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

  

当一个表达式的计算结果写入内存位置,而另一个评估结果读取或修改了相同的存储位置时,这些表达式就被认为是冲突的。除非[[]]

然后:

  

存储位置

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

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

array是:

  

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

由于两个不同的对象不能具有相同的地址,因此它们及其构成对象不能具有相同的存储位置。这可以保证满足先前的要求。

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

请注意,为了使您的示例正确,联接也必须正确编写,但它与数组的相邻元素无关,而是在同一数组上进行操作,因此我认为这超出了范围问题。


个人笔记:顺便说一句。如果不能保证这一点,将严重限制如果不在标准库中渲染无用的并行计算。

答案 2 :(得分:0)

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

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

这里是一个示例:false sharing SO question/answer

答案 3 :(得分:0)

同时访问单独线程上的连续元素是安全的,但是如果频繁发生,则可能会导致代码中的性能问题。现代CPU的并行性。

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

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

在不同线程上访问单独的字节只会在它们位于同一缓存行中时才导致重新加载。并且由于高速缓存行是连续的,因此从单独的线程访问连续的元素通常将导致必须将内存重新加载到高速缓存中。 This is called false sharing, and should be avoided in parallel code if performance is a concern.

话虽如此,如果这种情况很少发生,可能就可以了,您应该在优化代码之前进行基准测试并测试您的代码。