I have read total modification order means modification to same atomic variable twice in single thread using Relaxed memory ordering guarantees that modification happens in same order. It's only if different variables are modified then order is unpredictable. Am I right here?
static X: AtomicI32 = AtomicI32::new(0);
fn a() {
X.fetch_add(5, Relaxed);
X.fetch_add(10, Relaxed);
}
fn b() {
let a = X.load(Relaxed);
let b = X.load(Relaxed);
println!("{a} {b}");
}
That means output can never be 10 15.
Also can anyone please explain me what can go wrong with below code if we use Relaxed ordering. I am confused as what operations compiler can reorder since all operations are dependent on previous operations.
fn get_data() -> &'static Data {
static PTR: AtomicPtr<Data> = AtomicPtr::new(std::ptr::null_mut());
let mut p = PTR.load(Acquire);
if p.is_null() {
p = Box::into_raw(Box::new(generate_data()));
if let Err(e) = PTR.compare_exchange(
std::ptr::null_mut(), p, Release, Acquire) {
drop(unsafe{ Box::from_raw(p) });
p = e;
}
}
unsafe { &*p }
}
Say you do b = load(a) and then c = load(b) in your favorite language. The compiler can't reorder these, but your hardware might speculatively load stuff. It might make an educated guess as to what b will be and start loading from that address before it loads from a. When the load from a completes, it might see that it got b and succeed. But it could be that a was written later and the data it loaded from b didn't match.
This is an actual thing that can happen on some processors. I think DEC Alpha would do things like this if you let it. (Sadly we don't have much experience with this as a community because of the dominance of x86.) You need an "address dependency barrier." You can look around old Linux kernel mailing list threads that discuss these issues (before C had a memory model).
AtomicPtr only the pointer is the variable protected by atomic. The memory it points to (of type Data) isn't covered.
With Relaxed first thread that calls compare_exchange() will not make such Data reliably available to other threads. A second thread could get from load() the pointer but any attempt to access Data is considered undefined behaviour.
--
Extra pedantic bit.
"total modification order" could be confused with meaning SeqCst so better to say "total order"
Three threads call a() and you could get that output.