Crates.io | ptr_cell |
lib.rs | ptr_cell |
version | 2.2.1 |
source | src |
created_at | 2024-03-20 20:45:20.759719 |
updated_at | 2024-06-16 22:55:53.412846 |
description | Thread-safe cell based on atomic pointers |
homepage | |
repository | https://github.com/KDFJW/ptr_cell |
max_upload_size | |
id | 1180950 |
size | 39,251 |
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
Familiarity: PtrCell
's API was modelled after std
's Cell
Easy Concurrency: No more Arc<Mutex<T>>
, Arc::clone()
, and Mutex::lock().expect()
! Leave
the data static and then point to it when you need to. It's a single instruction on most modern
platforms
PtrCell
must first be allocated using
Box
. 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 stackJust add the crate using Cargo:
cargo add ptr_cell
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))
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
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(), 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,
}
}
}
}
Yes, please! See CONTRIBUTING.md
Authors of merged pull requests will be rewarded with snacks
Copyright 2024 Nikolay Levkovsky
Individual contributions are copyright by the respective contributors
This project is licensed under Creative Commons CC0 1.0 Universal (CC0 1.0) as found in LICENSE.txt. CC0 is a public domain dedication tool provided by Creative Commons