pub struct AtomicRegister<T: Default + From<u64> + Into<u64>> { /* private fields */ }Expand description
A shared-memory register, backed by an 64 bits of “atomic” memory.
This object works by serializing data and storing it in an
AtomicU64, and so can only be used to store small amounts
of data.
§Atomics and Memory Ordering
Unfortunately, the theoretical atomic memory model in which atomic really means linearizable, is not the same as the model used by languages such as Rust and C++. In practice, the compiler and hardware optimizations that make life livable come at the cost of potentially re-ordering memory accesses. The strictest consistency model that we can ask for in Rust is sequential consistency, which means that all processes perform operations in a sequential order, but the relative order of operations perfomed by different processes is undefined.
As a result, operations performed on an AtomicRegister are only
guaranteed to be sequentially consistent, not necessarily lineariazable.
Thankfully, it was recently shown by Perrin, Petrolia, Mostefaoui, and Jard
[PPM+2016] that objects that would be
linearizable if they were implemented on top of linearizable memory become
sequentially consistent if implemented on top of sequentially consistent
memory. This means that, while implemenations of linearizable algorithms
from AtomicRegister objects may fail to be linearizable, they will at least
be sequentially consistent, and will retain all other properties such as
wait-freedom.
§Linearizability
For a register that guarantees linearizability at the cost of lock-freedom,
see MutexRegister.
§Examples
A simple spinlock.
use std::sync::Arc;
use std::{hint, thread};
use todc_mem::register::{AtomicRegister, Register};
let register: Arc<AtomicRegister<u64>> = Arc::new(AtomicRegister::new());
let register_clone = register.clone();
let thread = thread::spawn(move || {
register_clone.write(1)
});
while register.read() == 0 {
hint::spin_loop();
}
thread.join().unwrap();Although space is limited, it is still possible to store any type that can
be converted to u64 and back again.
use heapless::String;
use todc_mem::register::{AtomicRegister, Register};
// A String with a fixed capacity of 64 bits
#[derive(Clone, Debug, Default, PartialEq)]
struct TinyString(String<8>);
impl From<TinyString> for u64 {
fn from(string: TinyString) -> Self {
// -- snipped --
}
}
impl From<u64> for TinyString {
fn from(value: u64) -> Self {
// -- snipped --
}
}
let register: AtomicRegister<TinyString> = AtomicRegister::new();
let empty = TinyString(String::from(""));
assert_eq!(register.read(), empty);
let greeting = TinyString(String::from("hi"));
register.write(greeting.clone());
assert_eq!(register.read(), greeting);
let emojis = TinyString(String::from("👋🦀"));
register.write(emojis.clone());
assert_eq!(register.read(), emojis);Trait Implementations§
Source§impl<T: Default + From<u64> + Into<u64>> Register for AtomicRegister<T>
impl<T: Default + From<u64> + Into<u64>> Register for AtomicRegister<T>
Source§fn new() -> Self
fn new() -> Self
Creates a new register containing the default value of T.
§Examples
use todc_mem::register::{AtomicRegister, Register};
let register: AtomicRegister<u64> = AtomicRegister::new();
assert_eq!(register.read(), u64::default());Source§fn read(&self) -> T
fn read(&self) -> T
Returns the value currently contained in the register.
§Examples
use todc_mem::register::{AtomicRegister, Register};
let register: AtomicRegister<u64> = AtomicRegister::new();
assert_eq!(register.read(), 0);Source§fn write(&self, value: T)
fn write(&self, value: T)
Sets contents of the register to the specified value.
§Examples
use todc_mem::register::{AtomicRegister, Register};
let register: AtomicRegister<u64> = AtomicRegister::new();
register.write(42);
assert_eq!(register.read(), 42);