Skip to main content

AtomicRegister

Struct AtomicRegister 

Source
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>

Source§

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

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)

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);
Source§

type Value = T

Auto Trait Implementations§

§

impl<T> !Freeze for AtomicRegister<T>

§

impl<T> RefUnwindSafe for AtomicRegister<T>
where T: RefUnwindSafe,

§

impl<T> Send for AtomicRegister<T>
where T: Send,

§

impl<T> Sync for AtomicRegister<T>
where T: Sync,

§

impl<T> Unpin for AtomicRegister<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for AtomicRegister<T>

§

impl<T> UnwindSafe for AtomicRegister<T>
where T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.