[Day22] Read Rust Atomics and Locks - Relaxed Ordering
by Mara Bos
From: Relaxed Ordering
To: Relaxed Ordering
At Topics: Chapter 3. Memory Ordering
Recall
When working with atomics (mutating data that's shared between threads), we have to explicitly tell the compiler and processor what they can and can't do with our atomic operations by the std::sync::atomic::Ordering
enum.
Which are:
- Relaxed ordering:
Ordering::Relaxed
- Release and acquire ordering:
Ordering::{Release, Acquire, AcqRel}
- Sequentially consistent ordering:
Ordering::SeqCst
Notes
- Atomic operations using relaxed memory ordering do not provide any happens-before relationship
- But it promises from the perspective of every other threads all modifications of the same atomic variable happen in a same order
- (Important) If using relaxed ordering, the compiler or CPU might reorder instructions! => Sometimes, release-acquire ordering is a must
See following example:
static X: AtomicI32 = AtomicI32::new(0); // possible order of modification X is 0 => 5 => 15 fn a() { X.fetch_add(5, Relaxed); X.fetch_add(10, Relaxed); } fn b() { let a = X.load(Relaxed); let b = X.load(Relaxed); let c = X.load(Relaxed); let d = X.load(Relaxed); println!("{a} {b} {c} {d}"); // possible results: "0 0 0 0", "0 0 5 15", "0 15 15 15" // impossible results: "0 5 0 15", "0 0 10 15" }
And with two threads:
use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; use std::thread; static X: AtomicI32 = AtomicI32::new(0); // possible order should be 0=>5=>15 or 0=>10=>15 fn a1() { X.fetch_add(5, Relaxed); } fn a2() { X.fetch_add(10, Relaxed); } // if we see eg. "0 10 10 15", "5" will never be printed out fn b() { let a = X.load(Relaxed); let b = X.load(Relaxed); let c = X.load(Relaxed); let d = X.load(Relaxed); println!("{a} {b} {c} {d}"); } fn main() { thread::scope(|s| { s.spawn(a1); s.spawn(a2); s.spawn(b); }); }