[Day25] Read Rust Atomics and Locks - Sequentially Consistent Ordering
by Mara Bos
At Topic: Chapter 3. Memory Ordering, Sequentially Consistent 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
- The strongest memory ordering
Ordering::SeqCst
- Each operation using SeqCst memory ordering contributes to a global order in which all operations are arranged
- And the total order of operations on shared variables is consistent with the total modification order of each individual variable
- Eg. if thread A writes a value to variable X and then thread B writes another value to variable X, all other threads will observe these writes to variable X in the same order (A's write first, then B's write) as they occurred in the program's execution.
- Because it is strictly stronger than acquire and release memory ordering, we can use
SeqCst + Release
orSeqCst + Acquire
and it still works fine
Only when both sides of a happens-before relationship use SeqCst ordering is it guaranteed to be consistent with the single total order of SeqCst operations.
- However, only rare cases need
SeqCst
, while acquire and release ordering suffice
Here is an example:
// source: https://github.com/m-ou-se/rust-atomics-and-locks/blob/main/examples/ch3-10-seqcst.rs use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::SeqCst; use std::thread; static A: AtomicBool = AtomicBool::new(false); static B: AtomicBool = AtomicBool::new(false); static mut S: String = String::new(); fn main() { let a = thread::spawn(|| { A.store(true, SeqCst); if !B.load(SeqCst) { unsafe { S.push('!') }; } }); let b = thread::spawn(|| { B.store(true, SeqCst); if !A.load(SeqCst) { unsafe { S.push('!') }; } }); a.join().unwrap(); b.join().unwrap(); }
The code tells us:
- Both store operations maybe happen before either of the load operations, and neither thread ends up accessing
S
- But both threads to access
S
is impossible, because in every possible single total order, the first operation will be a store operation preventing the other thread from accessingS
.