pub type ArcSwap<T> = ArcSwapAny<Arc<T>>;
Expand description
An atomic storage for Arc
.
This is a type alias only. Most of its methods are described on
ArcSwapAny
.
Aliased Type§
struct ArcSwap<T> { /* private fields */ }
Implementations§
source§impl<T: RefCnt, S: Strategy<T>> ArcSwapAny<T, S>
impl<T: RefCnt, S: Strategy<T>> ArcSwapAny<T, S>
sourcepub fn with_strategy(val: T, strategy: S) -> Self
pub fn with_strategy(val: T, strategy: S) -> Self
Constructs a new storage while customizing the protection strategy.
sourcepub fn into_inner(self) -> T
pub fn into_inner(self) -> T
Extracts the value inside.
sourcepub fn load_full(&self) -> T
pub fn load_full(&self) -> T
Loads the value.
This makes another copy of the held pointer and returns it, atomically (it is safe even when other thread stores into the same instance at the same time).
The method is lock-free and wait-free, but usually more expensive than
load
.
sourcepub fn load(&self) -> Guard<T, S>
pub fn load(&self) -> Guard<T, S>
Provides a temporary borrow of the object inside.
This returns a proxy object allowing access to the thing held inside. However, there’s
only limited amount of possible cheap proxies in existence for each thread ‒ if more are
created, it falls back to equivalent of load_full
internally.
This is therefore a good choice to use for eg. searching a data structure or juggling the pointers around a bit, but not as something to store in larger amounts. The rule of thumb is this is suited for local variables on stack, but not in long-living data structures.
Consistency
In case multiple related operations are to be done on the loaded value, it is generally
recommended to call load
just once and keep the result over calling it multiple times.
First, keeping it is usually faster. But more importantly, the value can change between the
calls to load, returning different objects, which could lead to logical inconsistency.
Keeping the result makes sure the same object is used.
struct Point {
x: usize,
y: usize,
}
fn print_broken(p: &ArcSwap<Point>) {
// This is broken, because the x and y may come from different points,
// combining into an invalid point that never existed.
println!("X: {}", p.load().x);
// If someone changes the content now, between these two loads, we
// have a problem
println!("Y: {}", p.load().y);
}
fn print_correct(p: &ArcSwap<Point>) {
// Here we take a snapshot of one specific point so both x and y come
// from the same one.
let point = p.load();
println!("X: {}", point.x);
println!("Y: {}", point.y);
}
sourcepub fn store(&self, val: T)
pub fn store(&self, val: T)
Replaces the value inside this instance.
Further loads will yield the new value. Uses swap
internally.
sourcepub fn compare_and_swap<C>(&self, current: C, new: T) -> Guard<T, S>where
C: AsRaw<T::Base>,
S: CaS<T>,
pub fn compare_and_swap<C>(&self, current: C, new: T) -> Guard<T, S>where C: AsRaw<T::Base>, S: CaS<T>,
Swaps the stored Arc if it equals to current
.
If the current value of the ArcSwapAny
equals to current
, the new
is stored inside.
If not, nothing happens.
The previous value (no matter if the swap happened or not) is returned. Therefore, if the
returned value is equal to current
, the swap happened. You want to do a pointer-based
comparison to determine it.
In other words, if the caller „guesses“ the value of current correctly, it acts like
swap
, otherwise it acts like load_full
(including
the limitations).
The current
can be specified as &Arc
, Guard
,
&Guards
or as a raw pointer (but not owned Arc
). See the
AsRaw
trait.
sourcepub fn rcu<R, F>(&self, f: F) -> Twhere
F: FnMut(&T) -> R,
R: Into<T>,
S: CaS<T>,
pub fn rcu<R, F>(&self, f: F) -> Twhere F: FnMut(&T) -> R, R: Into<T>, S: CaS<T>,
Read-Copy-Update of the pointer inside.
This is useful in read-heavy situations with several threads that sometimes update the data
pointed to. The readers can just repeatedly use load
without any locking.
The writer uses this method to perform the update.
In case there’s only one thread that does updates or in case the next version is
independent of the previous one, simple swap
or store
is enough. Otherwise, it may be needed to retry the update operation if some other thread
made an update in between. This is what this method does.
Examples
This will not work as expected, because between loading and storing, some other thread might have updated the value.
let cnt = ArcSwap::from_pointee(0);
thread::scope(|scope| {
for _ in 0..10 {
scope.spawn(|_| {
let inner = cnt.load_full();
// Another thread might have stored some other number than what we have
// between the load and store.
cnt.store(Arc::new(*inner + 1));
});
}
}).unwrap();
// This will likely fail:
// assert_eq!(10, *cnt.load_full());
This will, but it can call the closure multiple times to retry:
let cnt = ArcSwap::from_pointee(0);
thread::scope(|scope| {
for _ in 0..10 {
scope.spawn(|_| cnt.rcu(|inner| **inner + 1));
}
}).unwrap();
assert_eq!(10, *cnt.load_full());
Due to the retries, you might want to perform all the expensive operations before the rcu. As an example, if there’s a cache of some computations as a map, and the map is cheap to clone but the computations are not, you could do something like this:
fn expensive_computation(x: usize) -> usize {
x * 2 // Let's pretend multiplication is *really expensive expensive*
}
type Cache = HashMap<usize, usize>;
static CACHE: Lazy<ArcSwap<Cache>> = Lazy::new(|| ArcSwap::default());
fn cached_computation(x: usize) -> usize {
let cache = CACHE.load();
if let Some(result) = cache.get(&x) {
return *result;
}
// Not in cache. Compute and store.
// The expensive computation goes outside, so it is not retried.
let result = expensive_computation(x);
CACHE.rcu(|cache| {
// The cheaper clone of the cache can be retried if need be.
let mut cache = HashMap::clone(&cache);
cache.insert(x, result);
cache
});
result
}
assert_eq!(42, cached_computation(21));
assert_eq!(42, cached_computation(21));
The cost of cloning
Depending on the size of cache above, the cloning might not be as cheap. You can however
use persistent data structures ‒ each modification creates a new data structure, but it
shares most of the data with the old one (which is usually accomplished by using Arc
s
inside to share the unchanged values). Something like
rpds
or im
might do
what you need.
sourcepub fn map<I, R, F>(&self, f: F) -> Map<&Self, I, F>where
F: Fn(&I) -> &R + Clone,
Self: Access<I>,
pub fn map<I, R, F>(&self, f: F) -> Map<&Self, I, F>where F: Fn(&I) -> &R + Clone, Self: Access<I>,
Provides an access to an up to date projection of the carried data.
Motivation
Sometimes, an application consists of components. Each component has its own configuration structure. The whole configuration contains all the smaller config parts.
For the sake of separation and abstraction, it is not desirable to pass the whole configuration to each of the components. This allows the component to take only access to its own part.
Lifetimes & flexibility
This method is not the most flexible way, as the returned type borrows into the ArcSwap
.
To provide access into eg. Arc<ArcSwap<T>>
, you can create the Map
type directly. See
the access
module.
Performance
As the provided function is called on each load from the shared storage, it should generally be cheap. It is expected this will usually be just referencing of a field inside the structure.
Examples
use std::sync::Arc;
use arc_swap::ArcSwap;
use arc_swap::access::Access;
struct Cfg {
value: usize,
}
fn print_many_times<V: Access<usize>>(value: V) {
for _ in 0..25 {
let value = value.load();
println!("{}", *value);
}
}
let shared = ArcSwap::from_pointee(Cfg { value: 0 });
let mapped = shared.map(|c: &Cfg| &c.value);
crossbeam_utils::thread::scope(|s| {
// Will print some zeroes and some twos
s.spawn(|_| print_many_times(mapped));
s.spawn(|_| shared.store(Arc::new(Cfg { value: 2 })));
}).expect("Something panicked in a thread");
source§impl<T, S: Strategy<Arc<T>>> ArcSwapAny<Arc<T>, S>
impl<T, S: Strategy<Arc<T>>> ArcSwapAny<Arc<T>, S>
sourcepub fn from_pointee(val: T) -> Selfwhere
S: Default,
pub fn from_pointee(val: T) -> Selfwhere S: Default,
A convenience constructor directly from the pointed-to value.
Direct equivalent for ArcSwap::new(Arc::new(val))
.