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, 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
§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