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 (UB), 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 environments without the standard library (no_std), take a look at the standard Mutex and RwLock instead

§Offers:
  • Ease of use: The API is fairly straightforward
  • Performance: Core algorithms are at most a couple of instructions long
§Limits:
  • Access: To see what’s stored inside a cell, you must either take the value out of it or provide exclusive access (&mut) to the cell

§Usage

use ptr_cell::Semantics;

// Construct a cell
let cell: ptr_cell::PtrCell<u16> = 0x81D.into();

// Replace the value inside the cell
assert_eq!(cell.replace(Some(2047), Semantics::Relaxed), Some(0x81D));

// Check whether the cell is empty
assert_eq!(cell.is_empty(Semantics::Relaxed), false);

// Take the value out of the cell
assert_eq!(cell.take(Semantics::Relaxed), Some(2047))

§Semantics

PtrCell allows you to specify memory ordering semantics for its internal atomic operations through the Semantics enum. Choosing appropriate semantics is crucial for achieving the desired level of synchronization and performance. The available semantics are:

  • Ordered: Noticeable overhead, strict
  • Coupled: Acceptable overhead, intuitive
  • Relaxed: Little overhead, unconstrained

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 ordered through other means, like fences. As always, the documentation for each item contains more details

§Examples

Find the maximum value of a sequence of numbers by concurrently processing both of the sequence’s halves

use ptr_cell::Semantics;

fn main() {
    // Initialize an array of random numbers
    const VALUES: [u8; 11] = [47, 12, 88, 45, 67, 34, 78, 90, 11, 77, 33];

    // Construct a cell to hold the current maximum value
    let cell = ptr_cell::PtrCell::default();
    let maximum = std::sync::Arc::new(cell);

    // Slice the array in two
    let (left, right) = VALUES.split_at(VALUES.len() / 2);

    // Start a worker thread for each half
    let handles = [left, right].map(|half| {
        // Clone `maximum` to move it into the worker
        let maximum = std::sync::Arc::clone(&maximum);

        // Spawn a thread to run the maximizer
        std::thread::spawn(move || maximize_in(half, &maximum))
    });

    // Wait for the workers to finish
    for worker in handles {
        // Check whether a panic occured
        if let Err(payload) = worker.join() {
            // Thread panicked, propagate the panic
            std::panic::resume_unwind(payload)
        }
    }

    // Check the found maximum
    assert_eq!(maximum.take(Semantics::Relaxed), Some(90))
}

/// Inserts the maximum of `sequence` and `buffer` into `buffer`
///
/// At least one swap takes place for each value of `sequence`
fn maximize_in<T>(sequence: &[T], buffer: &ptr_cell::PtrCell<T>)
where
    T: Ord + Copy,
{
    // Iterate over the slice
    for &item in sequence {
        // Wrap the item to make the cell accept it
        let mut slot = Some(item);

        // Try to insert the value into the cell
        loop {
            // Replace the cell's value
            let previous = buffer.replace(slot, Semantics::Relaxed);

            // Determine whether the swap resulted in a decrease of the buffer's value
            match slot < previous {
                // It did, insert the old value back
                true => slot = previous,
                // It didn't, move on to the next item
                false => break,
            }
        }
    }
}

Structs§

  • Thread-safe cell based on atomic pointers

Enums§

  • Memory ordering semantics for atomic operations. Determines how value updates are synchronized between threads