Understanding memory_order_relaxed

一笑奈何 提交于 2019-12-06 08:33:37

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/31150809/understanding-memory-order-relaxed

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