std :: atomic应该易挥发吗? [英] Should std::atomic be volatile?

查看:47
本文介绍了std :: atomic应该易挥发吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

void f() {
  while(!stop.load(std::memory_order_{relaxed,acquire})) {
    do_the_job();
  }
}

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

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

据说挥发性和原子性是正交的,但是我有点困惑.编译器是否可以自由地缓存原子变量的值并展开循环?如果编译器可以展开循环,那么我想必须将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();
  ...
}

由于给定的内存顺序不会阻止先序操作移过原子负载,因此我认为可以在没有波动的情况下重新安排操作.

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();
  ...
}

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

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

  while(true) {
    do_the_job();
  }
}

永远不会有这样疯狂的实现,但我想这仍然是可能的情况.我认为防止这种情况的唯一方法是将volatile放到原子变量上,然后问这个问题.

我有很多猜测,请告诉我其中是否有错.

解决方案

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

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

但是,由于您使用的是std::memory_order_relaxed,这意味着编译器可以自由地从该原子变量/到其他原子和变量对存储器和原子进行重新排序.

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

您要避免的代码的转换不会是有效的转换,因为这会违反C ++内存模型:在第一种情况下,您要加载一个原子变量,然后再调用do_the_job,在第二个中,您有多个呼叫.转换后的代码观察到的行为可能有所不同.


以及 std :: memory_order :

的注释

与易失性的关系

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

此外,易失性访问不是原子的(并发读写是一种数据竞争),并且不对内存进行排序(非易失性存储器访问可以在易失性访问周围自由地重新排序)./p>

此位非易失性存储器访问可以在易失性访问周围自由地重新排序对于松弛原子也是如此,因为可以根据其他加载和存储对松弛加载和存储进行重新排序./p>

换句话说,用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;
}

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

I'm running a thread that runs until a flag is set.

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

void f() {
  while(!stop.load(std::memory_order_{relaxed,acquire})) {
    do_the_job();
  }
}

I wonder if the compiler can unroll loop like this (I don't want it to happen).

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

It is said that volatility and atomicity are orthogonal, but I'm a bit confused. Is the compiler free to cache the value of the atomic variable and unroll the loop? If the compiler can unroll the loop, then I think I have to put volatile to the flag, and I want to be sure.

Should I put volatile?


I'm sorry for being ambiguous. I (guess that I) understand what reordering is and what memory_order_*s mean, and I'm sure I fully understand what volatile is.

I think the while() loop can be transformed as an infinite if statements like this.

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();
  ...
}

Since the given memory orders don't prevent the sequenced-before operations from being moved past the atomic load, I think it can be rearranged if it's without volatile.

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();
  ...
}

If the atomic does not imply volatile, then I think the code can be even transformed like this at worst case.

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

  while(true) {
    do_the_job();
  }
}

There will never be such an insane implementation, but I guess it's still a possible situation. I think the only way to prevent this is to put volatile to the atomic variable and am asking about it.

There are a lot of guesses that I made, please tell me if there's anything wrong among them.

解决方案

Is the compiler free to cache the value of the atomic variable and unroll the loop?

The compiler cannot cache the value of an atomic variable.

However, since you are using std::memory_order_relaxed, that means the compiler is free to reorder loads and stores from/to this atomic variable with regards to other loads and stores.

Also note, that a call to a function whose definition is not available in this translation unit is a compiler memory barrier. That means the the call cannot not be reordered with regards to surrounding loads and stores and that all non-local variables must be reloaded from memory after the call, as if they were all marked volatile. (Local variables whose address was not passed elsewhere will not be reloaded though).

The transformation of code you would like to avoid would not be a valid transformation because that would violate C++ memory model: in the first case you have one load of an atomic variable followed by a call to do_the_job, in the second, you have multiple calls. The observed behaviour of the transformed code may be different.


And a note from std::memory_order:

Relationship with volatile

Within a thread of execution, accesses (reads and writes) to all volatile objects are guaranteed to not be reordered relative to each other, but this order is not guaranteed to be observed by another thread, since volatile access does not establish inter-thread synchronization.

In addition, volatile accesses are not atomic (concurrent read and write is a data race) and do not order memory (non-volatile memory accesses may be freely reordered around the volatile access).

This bit non-volatile memory accesses may be freely reordered around the volatile access is true for relaxed atomics as well, since relaxed load and stores can be reordered with regards to other loads and stores.

In other words, adorning your atomic with volatile would not change the behaviour of your code.


Regardless, C++11 atomic variables do not need to be marked with volatile keyword.


Here is an example how g++-5.2 honours atomic variables. The following functions:

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

Compiled with g++ -o- -Wall -Wextra -S -march=native -O3 -pthread -std=gnu++11 test.cc | c++filt > test.S produce the following assembly:

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

这篇关于std :: atomic应该易挥发吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆