c ++ atomic:函数调用会作为内存屏障吗?

时间:2016-11-16 20:41:09

标签: c++ atomic memory-barriers

我正在阅读这篇文章Memory Ordering at Compile Time,其中说:

  

事实上,大多数函数调用充当编译器障碍,   它们是否包含自己的编译器屏障。这排除了内联函数,使用pure属性声明的函数以及使用链接时代码生成的情况。除了这些情况之外,对外部函数的调用甚至比编译器障碍更强,因为编译器不知道函数的副作用是什么。

这是真实的陈述吗?想想这个样本 -

std::atomic_bool flag = false;
int value = 0;

void th1 () { // running in thread 1
  value = 1;
  // use atomic & release to prevent above sentence being reordered below
  flag.store(true, std::memory_order_release);
}

void th2 () { // running in thread 2
  // use atomic & acquire to prevent asset(..) being reordered above
  while (!flag.load(std::memory_order_acquire)) {}
  assert (value == 1);    // should never fail!
}

然后我们可以删除原子但用函数调用替换 -

bool flag = false;
int value = 0;

void writeflag () {
  flag = true;
}
void readflag () {
  while (!flag) {}
}
void th1 () {
  value = 1;
  writeflag(); // would function call prevent reordering?
}
void th2 () {
  readflag();  // would function call prevent reordering?
  assert (value == 1);    // would this fail???
}

有什么想法吗?

4 个答案:

答案 0 :(得分:2)

编译器障碍与内存障碍不同。编译器障碍阻止编译器跨越障碍移动代码。内存障碍(松散地说)阻止硬件跨屏障移动读写。对于原子,你需要两者,你还需要确保在读或写时不会撕裂值。

答案 1 :(得分:1)

正式,不,如果仅因为链接时代码生成是有效的实现选择而且不必是可选的。

还有第二次疏忽,以及逃脱分析。声称是"编译器不知道函数的副作用是什么。" ,但是如果没有指向我的局部变量的指针从我的函数,然后编译器确实知道没有其他函数更改它们。

答案 2 :(得分:1)

您混淆了用于线程间内存可见性的内存屏障和编译器屏障,后者不是线程设备,只是防止编译器副作用重新排序的设备(或技巧) >。

您的线程示例需要一个内存屏障。

您可以使用编译器屏障来确保以给定的顺序(在本地CPU上)执行内存方面的工作,以用于其他目的,例如基准测试,解决类型别名冲突,集成汇编代码或信号处理(用于仅在同一线程中处理的信号)。

答案 3 :(得分:0)

在第二个例子中,即使我们假设没有任何类型的重新排序,行为也是未定义的。

来自变量标志的写入和读取不是原子的,并且存在竞争条件 1 。没有重新排序并不能保证两个线程不会同时访问变量单元。当一个线程在函数readflag中触发while循环并读取标志,而另一个线程在writeflag中写入标志时,就会发生这种情况。

1 (引自:ISO / IEC 14882:2011(E)1.10多线程执行和数据竞赛21)
如果程序在不同的线程中包含两个冲突的动作,则程序的执行包含数据竞争, 其中至少有一个不是原子的,也不会发生在另一个之前。任何此类数据竞赛都会产生 未定义的行为