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
- Ease of use: The API is fairly straightforward
- Performance: The algorithms are at most a couple of instructions long
- 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
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.1.1"
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))
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, strictCoupled
: Acceptable overhead, intuitiveRelaxed
: 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
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,
}
}
}
}
Yes, please! See CONTRIBUTING.md
Either CC0 1.0 Universal or the Apache License 2.0. See LICENSE.md for more details