Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can non-atomic-load be reordered after atomic-acquire-load?

As known in since C++11 there are 6 memory orders, and in documentation written about std::memory_order_acquire:

  • http://en.cppreference.com/w/cpp/atomic/memory_order

memory_order_acquire

A load operation with this memory order performs the acquire operation on the affected memory location: no memory accesses in the current thread can be reordered before this load. This ensures that all writes in other threads that release the same atomic variable are visible in the current thread.

1. Non-atomic-load can be reordered after atomic-acquire-load:

I.e. it does not guarantee that non-atomic-load can not be reordered after acquire-atomic-load.

static std::atomic<int> X;
static int L;
...

void thread_func() 
{
    int local1 = L;  // load(L)-load(X) - can be reordered with X ?

    int x_local = X.load(std::memory_order_acquire);  // load(X)

    int local2 = L;  // load(X)-load(L) - can't be reordered with X
}

Can load int local1 = L; be reordered after X.load(std::memory_order_acquire);?

2. We can think that non-atomic-load can not be reordered after atomic-acquire-load:

Some articles contained a picture showing the essence of acquire-release semantics. That is easy to understand, but can cause confusion.

enter image description here

enter image description here

For example, we may think that std::memory_order_acquire can't reorder any series of Load-Load operations, even non-atomic-load can't be reordered after atomic-acquire-load.

3. Non-atomic-load can be reordered after atomic-acquire-load:

Good thing that there is clarified: Acquire semantics prevent memory reordering of the read-acquire with any read or write operation which follows it in program order. http://preshing.com/20120913/acquire-and-release-semantics/

But also known, that: On strongly-ordered systems (x86, SPARC TSO, IBM mainframe), release-acquire ordering is automatic for the majority of operations.

And Herb Sutter on page 34 shows: https://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c

enter image description here

4. I.e. again, we can think that non-atomic-load can not be reordered after atomic-acquire-load:

I.e. for x86:

  • release-acquire ordering is automatic for the majority of operations
  • Reads are not reordered with any reads. (any - i.e. regardless of older or not)

So can non-atomic-load be reordered after atomic-acquire-load in C++11?

like image 556
Alex Avatar asked Jul 30 '16 18:07

Alex


2 Answers

I believe this is the correct way to reason about your example within the C++ standard:

  1. X.load(std::memory_order_acquire) (let's call it "operation (A)") may synchronize with a certain release operation on X (operation (R)) - roughly, the operation that assigned the value to X that (A) is reading.

[atomics.order]/2 An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release sequence headed by A.

  1. This synchronizes-with relationship may help establish a happens-before relationship between some modification of L and the assignment local2 = L. If that modification of L happens-before (R), then, due to the fact that (R) synchronizes-with (A) and (A) is sequenced-before the read of L, that modification of L happens-before this read of L.

  2. But (A) has no effect whatsoever on the assignment local1 = L. It neither causes data races involving this assignment, nor helps prevent them. If the program is race-free, then it must necessarily employ some other mechanism to ensure that modifications of L are synchronized with this read (and if it's not race-free, then it exhibits undefined behavior and the standard has nothing further to say about it).


It is meaningless to talk about "instruction reordering" within the four corners of the C++ standard. One may talk about machine instructions generated by a particular compiler, or the way those instructions are executed by a particular CPU. But from the standard's standpoint, these are merely irrelevant implementation details, as long as that compiler and that CPU produce observable behavior consistent with one possible execution path of an abstract machine described by the standard (the As-If rule).

like image 144
Igor Tandetnik Avatar answered Sep 20 '22 23:09

Igor Tandetnik


The reference you cited is pretty clear: you can't move reads before this load. In your example:

static std::atomic<int> X;
static int L;


void thread_func() 
{
    int local1 = L;  // (1)
    int x_local = X.load(std::memory_order_acquire);  // (2)
    int local2 = L;  // (3)
}

memory_order_acquire means that (3) cannot happen before (2) (the load in (2) is sequenced before thr load in (3)). It says nothing about the relationship between (1) and (2).

like image 38
Barry Avatar answered Sep 17 '22 23:09

Barry