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 afterstd
’s Cell -
Easy Concurrency: No more
Arc<Mutex<T>>
,Arc::clone()
, andMutex::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 usingBox
. 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:
Variant | Overhead | Synchronization |
---|---|---|
Relaxed | Negligible | None |
Coupled | Acceptable | Intuitive |
Ordered | Noticeable | Strict |
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