Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding memory_order_relaxed

I am trying to understand the specifics of memory_order_relaxed. I am referring to this link : CPP Reference.

#include <future>
#include <atomic>

std::atomic<int*> ptr {nullptr};

void fun1(){
        ptr.store(new int{0}, std::memory_order_relaxed);
}

void fun2(){
        while(!ptr.load(std::memory_order_relaxed));
}

int main(){
        std::async(std::launch::async, fun1);
        std::async(std::launch::async, fun2);
}

Question 1: In the code above, is it technically possible for fun2 to be in an infinite loop where it sees the value of ptr as nullptr even if the thread that sets ptr has finished running?

If suppose, I change the code above to something like this instead:

#include <future>
#include <atomic>

std::atomic<int> i {0};
std::atomic<int*> ptr {nullptr};

void fun1(){
        i.store(1, std::memory_order_relaxed);
        i.store(2, std::memory_order_relaxed);
        ptr.store(new int{0}, std::memory_order_release);

}

void fun2(){
        while(!ptr.load(std::memory_order_acquire));
        int x = i.load(std::memory_order_relaxed);
}

int main(){
        std::async(std::launch::async, fun1);
        std::async(std::launch::async, fun2);
}

Related Question: Is it possible in the code above for fun2 to see the value of atomic i as 1 or is it assured that it will see the value 2?

like image 379
MS Srikkanth Avatar asked Jul 01 '15 00:07

MS Srikkanth


People also ask

What is memory order relaxed?

Memory orderings specify the way atomic operations synchronize memory. In its weakest Ordering::Relaxed , only the memory directly touched by the operation is synchronized.

What is Memory_order_seq_cst?

The default is std::memory_order_seq_cst which establishes a single total ordering over all atomic operations tagged with this tag: all threads see the same order of such atomic operations and no memory_order_seq_cst atomic operations can be reordered.

What is memory model in C ++ 11?

C++11 Memory Model. A memory model, a.k.a memory consistency model, is a specification of the allowed behavior of multithreaded programs executing with shared memory [1].


1 Answers

An interesting observation is that, with your code, there is no actual concurrency; i.e. fun1 and fun2 run sequentially, the reason being that, under specific conditions (including calling std::async with the std::launch::async launch policy), the std::future object returned by std::async has its destructor block until the launched function call returns. Since you disregard the return object, its destructor is called before the end of the statement. Had you reversed the two statements in main() (i.e. launch fun2 before fun1), your program would have been caught in an infinite loop since fun1 would never run.

This std::future wait-upon-destruction behavior is somewhat controversial (even within the standards committee) and since I assume you didn't mean that, I will take the liberty to rewrite the 2 statements in main for (both examples) to:

auto tmp1 = std::async(std::launch::async, fun1);
auto tmp2 = std::async(std::launch::async, fun2);

This will defer the actual std::future return object destruction till the end of main so that fun1 and fun2 get to run asynchronously.

is it technically possible for fun2 to be in an infinite loop where it sees the value of ptr as nullptr even if the thread that sets ptr has finished running?

No, this is not possible with std::atomic (on a real platform, as was mentioned in the comments section). With a non-std::atomic variable, the compiler could (theoretically) have chosen to keep the value in register only, but a std::atomic is stored and cache coherency will propagate the value to other threads. Using std::memory_order_relaxed is fine here as long as you don't dereference the pointer.

Is it possible in the code above for fun2 to see the value of atomic i as 1 or is it assured that it will see the value 2?

It is guaranteed to see value 2 in variable x.
fun1 stores two different values to the same variable, but since there is a clear dependency, these are not reordered.

In fun1, the ptr.store with std::memory_order_release prevents the i.store(2) with std::memory_order_relaxed from moving down below its release barrier. In fun2, the ptr.load with std::memory_order_acquire prevents the i.load with std::memory_order_relaxed from moving up across its acquire barrier. This guarantees that x in fun2 will have value 2.

Note that by using std::memory_order_relaxed on all atomics, it would be possible to see x with value 0, 1 or 2, depending on the relative ordering of access to atomic variable i with regards to ptr.store and ptr.load.

like image 82
LWimsey Avatar answered Oct 19 '22 07:10

LWimsey