Should std::atomic be volatile?

安稳与你 提交于 2019-11-30 11:09:34

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

If do_the_job() does not change stop, it doesn't matter if the compiler can unroll the loop, or not.

std::memory_order_relaxed just makes sure each operation is atomic, but it does not prevent reordering accesses. That means if another thread sets stop to true, the loop may continue to execute a few times, because the accesses may be reordered. So it is the same situation as with an unrolled loop: do_the_job() may be executed a few times after another thread has set stop to true.

So no, don't use volatile, use std::memory_order_acquire and std::memory_order_release.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!