Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::memory_order_relaxed confusion

I was reading this article from GCC Wiki about C++ memory barriers (and its awesome).
It was pretty straightforward until I got to this point:

The opposite approach is std::memory_order_relaxed. This model allows for much less synchronization by removing the happens-before restrictions. These types of atomic operations can also have various optimizations performed on them, such as dead store removal and commoning. So in the earlier example:

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

Since threads don't need to be synchronized across the system, either assert in this example can actually FAIL.

Ok, this is straightforward as well, lets carry on..

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

The assert cannot fail. Once the store of 2 is seen by thread 2, it can no longer see the value 1. This prevents coalescing relaxed loads of one variable across relaxed loads of a different reference that might alias.

This is what confused me, why y cannot load the value 2 and z load the value 1 (and cause assertion to fail), since the ordering is not synchronized in thread 1?

like image 385
Eduard Rostomyan Avatar asked Oct 12 '20 04:10

Eduard Rostomyan


1 Answers

Relaxed ordering is relative to the ordering of operations with regard to other memory accesses, not ordering relative to the atomic being relax-modified. In your first case, the fact that you can see 10 in x doesn't mean anything with regard to the value of y. And vice-versa.

But your second case is different, because it's affecting the same atomic object.

[intro.races]/10 tells us that, within a thread, if one operation is sequenced before another, then that operation "happens before" the other. And [intro.races]/14-17 outline the following behavior with regard to atomics:

The four preceding coherence requirements effectively disallow compiler reordering of atomic operations to a single object, even if both operations are relaxed loads.

And that's what you have here. All of the modifications are happening to the same object, so they must happen in some order. Even if that order cannot be determined exactly, the order must respect the "happens before" relationships of the code.

Thread 1's two operations are ordered by a "happens before" relationship. And Thread 2's operations are themselves ordered by a "happens before" relationship.

Since they're all acting on the same atomic object, if y gets the value of 2, then it must have "happened after" x was set to 2. So the order of access for x must have been "x = 1, x = 2, read x". And since the last read of x happens after the first read of x, the value it gets cannot be 1.

like image 159
Nicol Bolas Avatar answered Nov 11 '22 19:11

Nicol Bolas