I’ve been digging into the documentation for Rust's Atomic types at Rust Atomics and Locks by Mara Bos, but I’m still struggling to wrap my head around how Acquire and Release ordering actually works.
I have a few specific points of confusion that I’m trying to clear up:
- Do they always come in pairs? Must
AcquireandReleasealways be used together, or can I just use one at the end of a sequence? - Does the order matter for preceding operations? If I use
Acquirefor the finalload, does it matter what ordering I used for the operations that came before it? - What about
Relaxed? Can I useRelaxedfor the preceding operations as long as the final operation usesAcquire?
My current mental model is that as long as the final load uses Acquire, the previous operations don't strictly need to be synchronized. I assume Relaxed provides just enough logic to ensure the threads are running, but it doesn't guarantee the same memory visibility.
Honestly, I’m still not entirely sure how this flows in practice.
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering::{Acquire, Relaxed, Release}};
use std::thread;
static VALUE: AtomicU64 = AtomicU64::new(0);
static READY: AtomicBool = AtomicBool::new(false);
fn main() {
thread::spawn(|| {
VALUE.store(42, Relaxed); // We use Relaxed here because we don't care about memory ordering for this value, and it's more performant. Since it's not a flag, Relaxed is sufficient.
READY.store(true, Release); // This is our final write. We use Release to ensure that the other thread sees the previous store to VALUE.
});
while !READY.load(Acquire) {} // Since this is the first load from a different thread, we use Acquire. This ensures that subsequent reads in this thread will see the correct values, even those marked as Relaxed.
println!("{}", VALUE.load(Relaxed)); // This is guaranteed to be 42.
}
Here are my thoughts:
- I understand that Release only affects subsequent Acquire operations (whether in the same thread or a different thread, as long as they synchronize on the same atomic variable).
- Acquire affects the local thread where it is called. Once we load the READY flag with Acquire, the memory synchronization is established. For subsequent loads in this thread, even if they target a different atomic value, we don't necessarily need another Acquire because the happens-before relationship is already set.
Am I correct ?