3 unstable releases
0.2.1 | Jul 25, 2024 |
---|---|
0.2.0 | Jul 19, 2024 |
0.1.0 | Jul 16, 2024 |
#1836 in Data structures
51KB
798 lines
Locker Room
Readers-writer access to individual cells of your collection!
LockerRoom and LockerRoomAsync
The central features of the crate is implemented by these structures. More specifically, they provide such functionality:
- Shared readers access to single cell of collection with
LockerRoom::read_cell
andLockerRoomAsync::read_cell
; - Exclusive writer access to single cell of collection with
LockerRoom::write_cell
andLockerRoomAsync::write_cell
; - Exclusive writer access to whole collection with
LockerRoom::lock_room
andLockerRoomAsync::lock_room
.
But LockerRoomAsync
is optional - you need to enable feature async
to use it. It depends on
tokio
's RwLock
.
LockerRoom example
let v = vec![0, 1, 2, 3, 4, 5];
let locker_room: LockerRoom<_> = v.into();
let locker_room = Arc::new(locker_room);
thread::scope(|scope| {
scope.spawn(|| *locker_room.write_cell(0).unwrap() += 1);
scope.spawn(|| *locker_room.write_cell(0).unwrap() += 2);
});
assert_eq!(3, *locker_room.read_cell(0).unwrap());
LockerRoomAsync example
let v = vec![0, 1, 2, 3, 4, 5];
let locker_room: LockerRoomAsync<_> = v.into();
let locker_room = Arc::new(locker_room);
let locker_room_cloned = Arc::clone(&locker_room);
let join1 = spawn(async move { *locker_room_cloned.write_cell(0).await.unwrap() += 1 });
let locker_room_cloned = Arc::clone(&locker_room);
let join2 = spawn(async move { *locker_room_cloned.write_cell(0).await.unwrap() += 2 });
join!(join1, join2);
assert_eq!(3, *locker_room.read_cell(0).await.unwrap());
Deadlock example
Carefully block multiple cells in one scope. Otherwise, situation like this may occur:
// Thread 1 | // Thread 2
let _w1 = locker_room.write_cell(0); |
| let _w1 = locker_room.write_cell(1);
// will block
let _w2 = locker_room.write_cell(1); |
| // will deadlock
| let _w2 = locker_room.write_cell(0);
Collections?
By default you can create LockerRoom
and LockerRoomAsync
from array
, Vec
, VecDeque
, HashMap
and BTreeMap
.
But the crate provides traits, by which implementing to your collection, you can make it compatible with LockerRoom
and LockerRoomAsync
.
Collection
Crucial part of the crate that helps your collection to be compatible with LockerRoom
and LockerRoomAsync
.
Just implement it into your collection and everything will work!
Example
Let's implement the trait for the struct from Index
's example:
enum Nucleotide {
C,
A,
G,
T,
}
struct NucleotideCount {
pub a: usize,
pub c: usize,
pub g: usize,
pub t: usize,
}
impl Collection for NucleotideCount {
type Output = usize;
type Idx = Nucleotide;
type ShadowLocks = NucleotideShadowLocks;
fn index(&self, index: impl Borrow<Self::Idx>) -> Option<&Self::Output> {
Some(match index.borrow() {
Nucleotide::A => &self.a,
Nucleotide::C => &self.c,
Nucleotide::G => &self.g,
Nucleotide::T => &self.t,
})
}
fn index_mut(&mut self, index: impl Borrow<Self::Idx>) -> Option<&mut Self::Output> {
Some(match index.borrow() {
Nucleotide::A => &mut self.a,
Nucleotide::C => &mut self.c,
Nucleotide::G => &mut self.g,
Nucleotide::T => &mut self.t,
})
}
fn indices(&self) -> impl Iterator<Item = Self::Idx> {
[Nucleotide::A, Nucleotide::C, Nucleotide::G, Nucleotide::T].into_iter()
}
fn shadow_locks(&self) -> Self::ShadowLocks {
Default::default()
}
}
struct NucleotideShadowLocks {
a: RwLock<()>,
c: RwLock<()>,
g: RwLock<()>,
t: RwLock<()>,
}
impl ShadowLocksCollection for NucleotideShadowLocks {
type Idx = Nucleotide;
fn index(&self, index: impl Borrow<Self::Idx>) -> Option<&RwLock<()>> {
Some(match index.borrow() {
Nucleotide::A => &self.a,
Nucleotide::C => &self.c,
Nucleotide::G => &self.g,
Nucleotide::T => &self.t,
})
}
fn update_indices(&mut self, _indices: impl Iterator<Item = Self::Idx>) {
// No need to reindex because NucleotideShadowLocks has static structure.
}
}
If feature async
is enabled, Collection
must also include Collection::ShadowLocksAsync
type and Collection::shadow_locks_async
method.
Thus ShadowLocksCollectionAsync
must be implemented.
impl Collection for NucleotideCount {
// ...
type ShadowLocksAsync = NucleotideShadowLocksAsync;
// ...
fn shadow_locks_async(&self) -> Self::ShadowLocksAsync {
Default::default()
}
}
struct NucleotideShadowLocksAsync {
a: tokio::sync::RwLock<()>,
c: tokio::sync::RwLock<()>,
g: tokio::sync::RwLock<()>,
t: tokio::sync::RwLock<()>,
}
impl ShadowLocksCollectionAsync for NucleotideShadowLocksAsync {
type Idx = Nucleotide;
fn index(&self, index: impl Borrow<Self::Idx>) -> Option<&tokio::sync::RwLock<()>> {
Some(match index.borrow() {
Nucleotide::A => &self.a,
Nucleotide::C => &self.c,
Nucleotide::G => &self.g,
Nucleotide::T => &self.t,
})
}
fn update_indices(&mut self, _indices: impl Iterator<Item = Self::Idx>) {
// No need to reindex because NucleotideShadowLocksAsync has static structure.
}
}
Dependencies
~0–5.5MB
~19K SLoC