[Day16] Read Rust Atomics and Locks - Atomic Load and Store Operations
by Mara Bos
From: Atomic Load and Store Operations
To: Example: Lazy Initialization
At Topics: Chapter 2. Atomics
Recall
- Atomic operations allow for different threads to safely read and modify the same variable
- By interior mutability, they allow modification through a shared reference
- Every atomic operation takes an argument of type
std::sync::atomic::Ordering
, which determines what guarantees we get about the relative ordering of operations. - The simplest variant with the fewest guarantees is
Relaxed
.Relaxed
still guarantees consistency on a single atomic variable, but does not promise anything about the relative order of operations between different atomic variables.
Notes
Atomic Load and Store Operations
Eg.
impl AtomicI32 { // loads the value stored in the atomic variable // Note: its parameter is type Ordering pub fn load(&self, ordering: Ordering) -> i32; // stores a new value in it pub fn store(&self, value: i32, ordering: Ordering); }
- Some common use cases: a stop flag, process reporting, Lazy Initialization
Example of Lazy Initialization:
There is a runtime constant value which we don't expect to change. Therefore, we can request or calculate it only the first time we need it, and then remember the result.
Runtime constant value: eg. the version of the OS, the total amount of memory, the 400th PI or values from a file
use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering::Relaxed; use std::thread; use std::time::Duration; fn get_x() -> u64 { // X is Allocated once and persist throughout the entire program's execution static X: AtomicU64 = AtomicU64::new(0); let mut x = X.load(Relaxed); // only the first thread runs at the first time if x == 0 { x = calculate_x(); // calculate 0 X.store(x, Relaxed); // make it available for future use } x } fn calculate_x() -> u64 { thread::sleep(Duration::from_secs(1)); 123 } fn main() { dbg!(get_x()); dbg!(get_x()); }
- The code above may cause "race" (not "data race")
- The Rust standard library provides exactly a functionality to wait for the first thread through
std::sync::Once
andstd::sync::OnceLock
.