My understanding about the happens-before relationship after reading Mara Bos's book is that Relaxed memory ordering only ensures this relationship in a single thread. Relaxed memory ordering does not ensure a happens-before relationship among multiple threads.
Here is a code snippet taken from that excellent book:
static X: AtomicI32 = AtomicI32::new(0);
static Y: AtomicI32 = AtomicI32::new(0);
fn a() {
//operation1
X.store(10, Relaxed);
//operation2
Y.store(20, Relaxed);
}
fn b() {
//operation3
let y = Y.load(Relaxed);
//operation4
let x = X.load(Relaxed);
println!("{x} {y}");
}
The function a()
is executed by thread1 and the function b()
is executed by thread2.
How can the output 0 20
be possible from this code snippet?
20 is the value of Y
. 20 sets to Y in thread1 by the operation2.
The operation3 must be executed to load the 20 from Y to y in thread2.
0 is the value of X . 0 is set to X during the initialization process which is outside of the thread.
X loads to x at operation4, X and x must be 0 to match the output.
So execution order of the operations is as follows:
2 -> 3 -> 4 -> 1. However, as Relaxed memory ordering ensures the happens-before relationship in thread1 that means 1 happens before 2. So this order of operation does not seem accurate. As a result, the output 0 20 seems impossible in practice.
I think the output 0 20 is still possible in theory if the execution of operation1 is not observed in thread2 but operation2 is observed in thread2.
Theoretically, the output 0 20 is possible only because there is no happens-before relationship between thread1 and thread2. This reasoning abstracts away all the nitty-gritty details of what happens inside the threads. Or there is no happens-before relationship between threads so thread 2 observes the operation1 and 2 might appear to happen in the opposite order.
What could be the more precise, sane, and logical reasoning for having the output 0 20 by code snippet?