Course contentsShow
← Rust
Lesson 1089 of 1,91010. Concurrency PrimitivesFree lesson

Memory Ordering and Cache Consistency

You will understand the implications of hardware memory models and how to select correct ordering constraints.

In this lesson, you will learn how CPU architecture constraints dictate the visibility of atomic operations across different processor cores.

The Illusion of Sequential Consistency

When we write code, we assume that lines execute in the order we wrote them. However, modern CPUs and compilers often reorder instructions to maximize throughput. When you use atomics in Rust, you are actually negotiating a contract with the hardware.

Cache Consistency and Memory Barriers

Every CPU core has its own local cache. If Thread A updates a value in its cache, that change is not immediately visible to Thread B. Hardware uses "cache coherency protocols" to synchronize these caches, but this synchronization is expensive.

Memory ordering tells the CPU how strictly it must enforce the sequence of reads and writes:

  • Relaxed: Only guarantees atomicity for the operation itself. Other threads might see the update to a variable, but they aren't guaranteed to see the "happens-before" relationship of other operations.
  • Acquire / Release: This is the standard pattern for synchronizing state. When Thread A performs a Release store, it "publishes" its previous writes to memory. When Thread B performs an Acquire load, it "synchronizes" with that store, ensuring it sees everything Thread A did before the release.

Consider this synchronization pattern:

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

let data_ready = Arc::new(AtomicBool::new(false));

// Thread 1: Producer
// ... prepare data ...
data_ready.store(true, Ordering::Release); // Ensures data writes are visible

// Thread 2: Consumer
if data_ready.load(Ordering::Acquire) {
    // Guaranteed to see the data prepared by Thread 1
}

Why not just use SeqCst everywhere?

SeqCst (Sequentially Consistent) is the strongest ordering. It ensures a global total order of operations across all threads. While this makes your code behave like an intuitive single-threaded sequence, it requires the CPU to emit heavy-duty memory barriers that stall pipeline execution and flush store buffers. By understanding the specific needs of your data (e.g., just signaling availability), you can use lighter orderings like Acquire/Release to drastically improve performance.

Key Takeaway: Memory ordering is a trade-off between strict synchronization guarantees and CPU performance; always use the weakest ordering that maintains your program's logical correctness.