[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); }); }

References