简体   繁体   中英

C++ atomic acquire / release operations what it actually means

I was going thru this page and trying to understand Memory model synchronization modes. In below example extracted from there:

 -Thread 1-       -Thread 2-
 y = 1            if (x.load() == 2)
 x.store (2);        assert (y == 1)

to which states that the store to 'y' happens-before the store to x in thread 1. Is 'y' variable here a normal global variable or is atomic?

Further if the load of 'x' in thread 2 gets the results of the store that happened in thread 1, it must all see all operations that happened before the store in thread 1 , even unrelated ones.

So what it means that x.store() operation would mean that all read / write to memory should have respective memory data values updated?

Then for std::memory_order_relaxed means " no thread can count on a specific ordering from another thread " - what does it means - is it that reordering will be done by compiler that value of y meynot be updated even though y.store() is called?

-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 */

For Acquire / release memory model is similar to the sequentially consistent mode, except it only applies a happens-before relationship to dependent variables.

Assuming 'x' and 'y' are initially 0:


 -Thread 1-
 y.store (20, memory_order_release);

 -Thread 2-
 x.store (10, memory_order_release);

 -Thread 3-
 assert (y.load (memory_order_acquire) == 20 && x.load (memory_order_acquire) == 0)

 -Thread 4-
 assert (y.load (memory_order_acquire) == 0 && x.load (memory_order_acquire) == 10)

What does it means in explicit terms?

 -Thread 1-       -Thread 2-
 y = 1            if (x.load() == 2)
 x.store (2);        assert (y == 1)
  1. Naturally, compiler may change order of operations that are not dependent to boost performance.
    But when std::memory_order_seq_cst is in action, any atomic operator works as memory barrier .
    This does not mean variable y is the atomic, compiler just guarantees that y = 1; happens before x.store (2); . If there was another thread 3 that manipulates variable y , assertion may fail due to the other thread.
    If my explanation is hard to understand(due to my poor English...) please check memory barrier & happened-before .
    If A happened before B relationship is made, all threads must see the side-effect of A if B 's side-effect has been sighted.
-Thread 1-
y.store (20, memory_order_relaxed)  // 1-1
x.store (10, memory_order_relaxed)  // 1-2

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

-Thread 3-
if (y.load (memory_order_relaxed) == 10)  // 3-1
  assert (x.load(memory_order_relaxed) == 10) /* assert B */
  1. To understand std::memory_order_relaxed , you need to understand data dependency . Clearly, x & y does not have any dependency to each other. So compiler may change the order of execution for thread 1 , unlike std::memory_order_seq_cst , where y.store(20) MUST executed before x.store(10) happens.
    Let's see how each assertion may fail. I've added tag for each instruction.

    assert A : 1-2 → 2-1 → assert A FAILED
    assert B : See post for detailed answer.

    In short summary, thread 3 may see final updated variable y and get 10, but not the side-effect of 1-2 . Even tho thread 2 must have seen it's side-effect in order to store 10 into y, compiler does not guarantee instruction's side effect must have synchronized between threads( happens-before )
    On the other hand, below example from the page is example of instruction's order preserved when instructions have data dependency. assert(y <= z) is guaranteed to be passed.
-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)

2-2. is it that reordering will be done by compiler that value of y may not be updated even though y.store() is called?
NO . As I've described in 2., it means compiler may change the order of instructions that does not have data dependency. Of course y must be updated when y.store() is called. After all, that's the definition of atomic instruction.

Assuming 'x' and 'y' are initially 0:


 -Thread 1-
 y.store (20, memory_order_release);

 -Thread 2-
 x.store (10, memory_order_release);

 -Thread 3-
 assert (y.load (memory_order_acquire) == 20 && x.load (memory_order_acquire) == 0)

 -Thread 4-
 assert (y.load (memory_order_acquire) == 0 && x.load (memory_order_acquire) == 10)
  1. Consistent mode requires happens-before relationship to all data. So under consistent mode, y.store() must happens-before x.store() or vice versa.
    If thread 3 's assert gets passed, it means y.store() happened before x.store() . So thread 4 must have seen y.load() == 20 before x.load() == 10 . Therefore assert is failed. Same thing happens if thread 4 's assert gets passed.
    acquire / release memory model does not enforce happens-before relationship to independent variables. So below order can be made without violating any rules.
    thread 4 y.load()thread 1 y.store()thread 3 y.load()thread 3 x.load()thread 4 x.load()
    Resulting both assertion gets passed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM