Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does this transitive happens-before use case need sequential consistency or will acquire/release suffice?

This snip is from Herb Sutter's Atomic Weapons talk slide from page number 19.

Slide from Atomic Weapons

If I am understanding this correctly, what Herb is saying is that, for the assert() in thread 3 to succeed, this has to follow sequential consistency. So the following code will not fail the assertion.

int g{0}; // normal int
std::atomic<int> x{0}, y{0}; // atomics

void thread1() {
    g = 1;
    x.store(1, std::memory_order_seq_cst);
}

void thread2() {
    if(x.load(std::memory_order_seq_cst) == 1)
        y.store(1, std::memory_order_seq_cst);
}

void thread3() {
    if(y.load(std::memory_order_seq_cst) == 1)
        assert( g == 1 );
}

But wouldn't this also work if release/acquire was used instead as follows?

int g{0}; // normal int
std::atomic<int> x{0}, y{0}; // atomics

void thread1() {
    g = 1;  // A
    x.store(1, std::memory_order_release);
}

void thread2() {
    if(x.load(std::memory_order_acquire) == 1)
        y.store(1, std::memory_order_release);
}

void thread3() {
    if(y.load(std::memory_order_acquire) == 1)
        assert( g == 1 );  // B
}

Q1 - Doesn't //A simply-happens-before //B ensure that the assertion is valid, since no other thread writes to g?

Q2 - Am I misunderstanding the purport of the slide, or is something wrong on the slide?

like image 348
Dhwani Katagade Avatar asked Oct 22 '25 15:10

Dhwani Katagade


1 Answers

Yes, acquire/release ordering would be sufficient here.

With acquire/release ordering and g being non-atomic, the code is okay because

  • T1:g = 1 is sequenced before T1:x = 1
  • T1:x = 1 synchronizes with T2:x == 1
  • T2:x == 1 is sequenced before T2:y = 1
  • T2:y = 1 is synchronizes with T3:y == 1
  • T3:y = 1 is sequenced before T3:g == 1

You have a happens before relationship running all the way from T1:g = 1 to T3:g == 1, and this means that there is no data race and that T3 has to read 1 (see [intro.races] p13).

Obviously, Thread 3 could also "run first", synchronization doesn't happen, and this whole happens before chain doesn't exist. However, if T3:y == 1 is true, the synchronizations must have taken place, T1:g = 1 happens before T3:assert(g == 1), and the assertion cannot fail.

Herb's mistake (possibly)

In the talk, Herb mentions that g is non-atomic. That makes it really hard to tell why he thinks that sequential consistency is required, considering that sequentially consistent operations on x don't provide any relevant extra guarantees regarding g.

Perhaps if g was atomic, one might suspect that g == 1 wouldn't always be true because of [intro.races] p13:

The value of an atomic object M, as determined by evaluation B, is the value stored by some unspecified side effect A that modifies M, where B does not happen before A.

In other words, assert(g == 1) could read arbitrarily far into the past modifications of g (but not into the future). This is the rule that misled Herb into thinking that this example requires sequential consistency.

However, a happens before chain as described above also imposes requirements on atomics:

If a side effect X on an atomic object M happens before a value computation B of M, then the evaluation B takes its value from X or from a side effect Y that follows X in the modification order of M.

In our case, g = 1 happens before assert(g == 1), so the assertion has to pass (or take a value of g that is written after g = 1, but no such write exists in this sample code).


Note: a happens before chain is upheld because an alternating pattern of sequenced before and synchronizes with is covered by inter-thread happens before.

like image 118
Jan Schultke Avatar answered Oct 25 '25 05:10

Jan Schultke