#cell #atomic #thread-safe #no-std

no-std ptr_cell

Thread-safe cell based on atomic pointers to externally stored data

9 stable releases

new 2.2.0 Apr 14, 2024
2.1.1 Apr 10, 2024
1.2.1 Mar 24, 2024

#129 in Concurrency

Download history 77/week @ 2024-03-15 295/week @ 2024-03-22 10/week @ 2024-03-29 294/week @ 2024-04-05

676 downloads per month

CC0-1.0 OR Apache-2.0

36KB
180 lines

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: The 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

Table of Contents

Installation

To add ptr_cell to your crate's dependencies, run the following command in your project directory:

cargo add ptr_cell

This will add ptr_cell to your Cargo.toml file, allowing you to use the library in your crate. Alternatively, you can do this by manually adding the following lines to the file:

[dependencies.ptr_cell]
version = "2.2.0"

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

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(), 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, ptr_cell::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,
            }
        }
    }
}

Contributing

Yes, please! See CONTRIBUTING.md

License

Either CC0 1.0 Universal or the Apache License 2.0. See LICENSE.md for more details

No runtime deps