c ++ atomic:函数调用会充当内存屏障吗?

c++ atomic: would function call act as memory barrier?

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

In fact, the majority of function calls act as compiler barriers, whether they contain their own compiler barrier or not.This excludes inline functions, functions declared with the pure attribute, and cases where link-time code generation is used. Other than those cases, a call to an external function is even stronger than a compiler barrier, since the compiler has no idea what the function’s side effects will be.

这是真的吗?想想这个例子 -

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!
}

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

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???
}

有什么想法吗?

正式地,不,如果只是因为 Link-Time Code Generation 是一个有效的实现选择并且不需要是可选的。

还有一个疏忽,那就是逃逸分析。声明是 "the compiler has no idea what the function’s side effects will be.",但是如果没有指向 my 局部变量的指针从 my[=18= 中逃逸] 函数,那么编译器确实知道没有其他函数会更改它们。

编译器屏障与内存屏障不同。编译器屏障阻止 编译器 将代码移过屏障。内存屏障(松散地说)阻止 硬件 跨屏障移动读取和写入。对于原子,你需要两者,你还需要确保值在读取或写入时不会被破坏。

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

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


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

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

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

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