std :: atomic应该是不稳定的吗?

Inb*_*ong 19 c++ atomic volatile c++11

我正在运行一个运行的线程,直到设置了一个标志.

std::atomic<bool> stop(false);

void f() {
  while(!stop.load(std::memory_order_{relaxed,acquire})) {
    do_the_job();
  }
}
Run Code Online (Sandbox Code Playgroud)

我想知道编译器是否可以像这样展开循环(我不希望它发生).

void f() {
  while(!stop.load(std::memory_order_{relaxed,acquire})) {
    do_the_job();
    do_the_job();
    do_the_job();
    do_the_job();
    ... // unroll as many as the compiler wants
  }
}
Run Code Online (Sandbox Code Playgroud)

据说波动率和原子性是正交的,但我有点困惑.编译器是否可以自由缓存原子变量的值并展开循环?如果编译器可以展开循环,那么我认为我必须加入volatile标志,我想确定.

我应该放volatile


我很抱歉暧昧.我(我猜我)理解重新排序是什么memory_order_*意思,我确定我完全理解它是什么volatile.

我认为while()循环可以转换为if像这样的无限语句.

void f() {
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  do_the_job();
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  do_the_job();
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  do_the_job();
  ...
}
Run Code Online (Sandbox Code Playgroud)

由于给定的内存顺序不会阻止序列化之前的操作移过原子载荷,我认为如果没有易失性,它可以重新排列.

void f() {
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  if(stop.load(std::memory_order_{relaxed,acquire})) return;
  ...
  do_the_job();
  do_the_job();
  do_the_job();
  ...
}
Run Code Online (Sandbox Code Playgroud)

如果原子并不意味着易失性,那么我认为在最坏的情况下代码甚至可以像这样转换.

void f() {
  if(stop.load(std::memory_order_{relaxed,acquire})) return;

  while(true) {
    do_the_job();
  }
}
Run Code Online (Sandbox Code Playgroud)

永远不会有这样疯狂的实施,但我想这仍然是一个可能的情况.我认为防止这种情况的唯一方法是放入volatile原子变量并询问它.

我做了很多猜测,请告诉我他们之间是否有什么问题.

Max*_*kin 8

编译器是否可以自由缓存原子变量的值并展开循环?

编译器无法缓存原子变量的值.

但是,由于您正在使用std::memory_order_relaxed,这意味着编译器可以自由地对此原子变量的加载和存储进行重新排序,与其他加载和存储相关.

另请注意,对此转换单元中定义不可用的函数的调用是编译器内存屏障.这意味着调用不能对周围的加载和存储进行重新排序,并且所有非局部变量必须在调用后从内存中重新加载,就好像它们都标记为volatile一样.(不会重新加载其地址未在其他地方传递的局部变量).

转换的代码,你想避免也不会是一个有效的改造,因为这将违反C++内存模型:在第一种情况下,你有一个原子变量,然后到呼叫的一个负载do_the_job,在第二,你有多个电话.观察到的变换代码的行为可能不同.


来自std :: memory_order的注释:

与挥发性的关系

在执行的一个线程中,对所有易失性对象的访问(读取和写入)保证不会相对于彼此重新排序,但是这个顺序不能保证被另一个线程观察到,因为易失性访问不会建立线程间同步.

此外,易失性访问不是原子的(并发读和写是数据竞争)并且不命令存储器(非易失性存储器访问可以在易失性访问周围自由重新排序).

这种位非易失性存储器访问可以在易失性访问周围自由地重新排序,对于轻松原子也是如此,因为可以关于其他加载和存储重新排序松弛的加载和存储.

换句话说,装饰原子并volatile不会改变代码的行为.


无论如何,C++ 11原子变量不需要用volatile关键字标记.


以下是g ++ - 5.2如何表达原子变量的示例.以下功能:

__attribute__((noinline)) int f(std::atomic<int>& a) {
    return a.load(std::memory_order_relaxed);
}

__attribute__((noinline)) int g(std::atomic<int>& a) {
    static_cast<void>(a.load(std::memory_order_relaxed));
    static_cast<void>(a.load(std::memory_order_relaxed));
    static_cast<void>(a.load(std::memory_order_relaxed));
    return a.load(std::memory_order_relaxed);
}

__attribute__((noinline)) int h(std::atomic<int>& a) {
    while(a.load(std::memory_order_relaxed))
        ;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译g++ -o- -Wall -Wextra -S -march=native -O3 -pthread -std=gnu++11 test.cc | c++filt > test.S生成以下程序集:

f(std::atomic<int>&):
    movl    (%rdi), %eax
    ret

g(std::atomic<int>&):
    movl    (%rdi), %eax
    movl    (%rdi), %eax
    movl    (%rdi), %eax
    movl    (%rdi), %eax
    ret

h(std::atomic<int>&):
.L4:
    movl    (%rdi), %eax
    testl   %eax, %eax
    jne .L4
    ret
Run Code Online (Sandbox Code Playgroud)

  • “这保证了存储对原子变量的可见性” 不能保证一个存储在任何有限的时间内对其他负载可见;我们所拥有的最好的是“实现应该使原子存储在合理的时间内对原子负载可见”,这是规范性的鼓励(“应该”),而不是要求。 (2认同)
  • @MikeMB是对的:根据ISO C++ 11,允许编译器*消除来自相同的非易失性原子变量的连续加载.Maxim:当前编译器不会这样做,或者将连续写入合并为实施质量问题,而不是因为标准禁止它.(例如,进度条更新存储可能会退出循环...)[为什么编译器不会合并多余的std :: atomic写入?](// stackoverflow.com/a/45971285).C++委员会正在研究新功能,因此程序员可以控制编译器何时/不允许优化原子. (2认同)