了解内存序列和std :: memory_order_relaxed

时间:2018-10-09 10:41:55

标签: c++ c++11 c++14 c++17

我正在研究C ++内存序列,但这非常令人困惑。

例如:

void sumUp(std::atomic<int>& sum, std::vector<int>& val)
{
   int tmpSum = 0;
   for(auto i = 0; i < 100; ++i) tmpSum += val[i];

   sum.fetch_add(tmpSum, std::memory_order_relaxed);
}

我不知道sum.fetch_add()之后是tmpSum += val[i]。 由于顺序不正确,sum.fetch_add()可以在tmpSum += val[i]之前运行吗?

那么总和 0 可能吗?

非常感谢。

4 个答案:

答案 0 :(得分:4)

false在单个线程的上下文中没有可观察到的效果:

让我们看看(memory_orderxa最初是b):

0

由于(1)和(2)不相互依赖,因此编译器可以对其重新排序。例如。可以做(1)->(2)或(2)->(1)

因为(3)取决于(1)((1)写入auto t1(std::atomic<int>& x, int& a, int& b) { a = 3; // 1 b = 5; // 2 x.store(a, std::memory_order_relaxed); // 3 } ,而(3)从a读取)编译器在(1)之前不能执行(3)。这与(3)中指定的存储顺序无关

由于(3)不依赖(2),通常在单线程模型中,编译器可以在(2)之前执行(3)。

但是,由于a是原子的,因此请考虑另一个线程这样做(xx引用了与提供给a的相同参数,并且最初都是b):

t1

此线程等待,直到0auto t2(std::atomic<int>& x, int& a, int& b) { while(x.load(std::memory_order_relaxed) == 3) // 4 assert(b == 5); // 5 } ,然后断言x3。现在,您可以看到如何在顺序单线程世界(2)和(3)中进行重新排序而没有任何可观察到的行为,但是在多线程模型中,(2)和(3)的顺序可能会对行为产生影响该程序的内容。

这就是b的作用:它指定是否可以在原子之前或之后重新排序而对单个线程没有任何影响的操作是否可以重新排序。原因是它们可能会对多线程程序产生影响。编译器只知道程序员,无法知道这一点,因此不知道额外的5参数。

使用memory_order时断言可能会失败,因为(2)可能会在(3)之后发生,但是使用memory_order(默认)时,断言将永远不会失败,因为memory_order_relaxed会在(3)之前发生)。


回到您的示例,无论您指定什么memory_order_seq_cst,都可以保证(2)将在memory_order之前发生,因为第二个依赖于第一个。 tmpSum += val[i];将影响不影响原子操作的指令的可能重新排序。例如。如果您有sum.fetch_add(tmpSum, std::memory_order_relaxed);


顺便说一句,官方术语是“之前”和“之后”


在现实世界中,硬件使事情变得更加复杂。操作可以在当前线程中以一个顺序出现,但是另一个线程可以以另一顺序查看,因此更严格的memory_order必须采取额外的措施来确保线程之间的顺序一致。


在此示例中,严格来说,如果使用int unrelated = 24,我们将具有未定义的行为,因为对memory_order的访问未在线程之间同步。

答案 1 :(得分:1)

  

所以总和可能为0?

不,不是。 std::memory_order_relaxed说,对sum 的并发访问通常没有顺序;同时,在此特定线程中,tmpSum的计算是在之前 fetch_add进行排序的,因此传递给fetch_add的值与在循环。因此,fetch_add不能保证所有线程中所有特定的线程方式tmpSum的添加顺序是什么,但这并不重要,因为整数加法转换了。但是语言语义学保证每次获取的值都是向量的总和。

答案 2 :(得分:1)

  

由于顺序不正确,sum.fetch_add()是否可以在tmpSum + = val [i]之前运行?所以总和可能是0?

不管作为as-if规则的结果导致的加载和存储的重新排序,程序都必须仍然按照仿照所编写指令的明确顺序进行“如果-如果”操作。

std::memory_order_relaxed的意思是:

  • 加法将相对于发生在不同线程中的其他原子操作原子地进行。

  • 总和的变化可能不会立即被另一个线程观察到,但是很快就会在某个时刻被观察到。

答案 3 :(得分:0)

不,为什么会这样?语句按顺序执行,这意味着首先执行100次添加操作,然后执行fetch_add。由于您正在使用原子,所以我猜您正在做一些多线程的工作。可能是,如果函数并行执行多次,则单个fetch_add会以任意顺序发生。