Crate ptr_cell

Source
Expand description

§Simple thread-safe cell

PtrCell is an atomic cell type that allows safe, concurrent access to shared data. No std, no data races, no nasal demons (undefined behavior), and most importantly, no locks

This type is only useful in scenarios where you need to update a shared value by moving in and out of it. If you want to concurrently update a value through mutable references and don’t require support for no_std, take a look at the standard Mutex and RwLock instead

§Offers:
  • Familiarity: PtrCell’s API was modelled after std’s Cell

  • Easy Concurrency: No more Arc<Mutex<T>>, Arc::clone(), and Mutex::lock().expect()! Leave the data static and then point to it when you need to. It’s a single instruction on most modern platforms

§Limitations:
  • Heap Allocation: Every value you insert into PtrCell must first be allocated using Box. Allocating on the heap is, computationally, a moderately expensive operation. To address this, the cell exposes a pointer API that can be used to avoid allocating the same values multiple times. Future releases will primarily rely on the stack

§Usage

use ptr_cell::{PtrCell, Semantics::Relaxed};

let cell: PtrCell<u16> = 0x81D.into();

assert_eq!(cell.replace(Some(2047), Relaxed), Some(0x81D));
assert_eq!(cell.is_empty(Relaxed), false);
assert_eq!(cell.take(Relaxed), Some(2047))

§Semantics

PtrCell allows you to specify memory ordering semantics for its internal atomic operations through the Semantics enum. Each variant is different in how it balances synchronization and performace. Here’s a comparison of the available semantics:

VariantOverheadSynchronization
RelaxedNegligibleNone
CoupledAcceptableIntuitive
OrderedNoticeableStrict

Coupled is what you’d typically use. However, other orderings have their use cases too. For example, the Relaxed semantics could be useful when the operations are already synchronized through other means, like fences. As always, the documentation for each item contains more details

§Examples

The code below finds the maximum value of a sequence by concurrently processing its halves. Notice how the code doesn’t read the shared value. Instead, it uses moves and corrects previous operations as new data comes in

use ptr_cell::{PtrCell, Semantics};
use std::sync::Arc;

fn main() {
    const VALUES: [u8; 11] = [47, 12, 88, 45, 67, 34, 78, 90, 11, 77, 33];

    let cell = PtrCell::default();
    let maximum = Arc::new(cell);

    let (left, right) = VALUES.split_at(VALUES.len() / 2);

    let handles = [left, right].map(|half| {
        let maximum = Arc::clone(&maximum);

        std::thread::spawn(move || maximize_in(half, &maximum))
    });

    for worker in handles {
        if let Err(payload) = worker.join() {
            std::panic::resume_unwind(payload)
        }
    }

    assert_eq!(maximum.take(Semantics::Relaxed), Some(90))
}

fn maximize_in<T>(sequence: &[T], buffer: &PtrCell<T>)
where
    T: Ord + Copy,
{
    for &item in sequence {
        let mut slot = Some(item);

        loop {
            let previous = buffer.replace(slot, Semantics::Relaxed);

            match slot < previous {
                true => slot = previous,
                false => break,
            }
        }
    }
}

Structs§

PtrCell
Thread-safe cell based on atomic pointers

Enums§

Semantics
Memory ordering semantics for atomic operations