Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should std::atomic be volatile?

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.

like image 652
Inbae Jeong Avatar asked Apr 08 '16 09:04

Inbae Jeong


People also ask

Is Atomic volatile?

Volatile and Atomic are two different concepts. Volatile ensures, that a certain, expected (memory) state is true across different threads, while Atomics ensure that operation on variables are performed atomically.

Is Atomic volatile C++?

Firstly, volatile does not imply atomic access. It is designed for things like memory mapped I/O and signal handling. volatile is completely unnecessary when used with std::atomic , and unless your platform documents otherwise, volatile has no bearing on atomic access or memory ordering between threads.

Is std :: atomic movable?

std::atomic is neither copyable nor movable.

Is std :: atomic thread safe?

Yes, it would be threadsafe. Assuming of course there are no bugs in the std::atomic implementation - but it's not usually hard to get right. This is exactly what std::atomic is meant to do.


1 Answers

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 
like image 95
Maxim Egorushkin Avatar answered Sep 16 '22 15:09

Maxim Egorushkin