10 stable releases
2.2.1 | Jun 16, 2024 |
---|---|
2.2.0 | Apr 14, 2024 |
1.2.1 | Mar 24, 2024 |
#216 in Concurrency
52 downloads per month
29KB
186 lines
Simple thread-safe cell for Rust
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
Table of Contents
Installation
Just add the crate using Cargo:
cargo add ptr_cell
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(), 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,
}
}
}
}
Contributing
Yes, please! See CONTRIBUTING.md
Authors of merged pull requests will be rewarded with snacks
License
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