pub struct ThreadMapX<V> { /* private fields */ }Expand description
Like ThreadMap,
this type encapsulates the association of ThreadIds to values of type V and is a simple and easy-to-use alternative
to the std::thread_local macro and the thread_local crate.
It differs from ThreadMap in that it contains a Mutex for each value, allowing the methods
Self::fold, Self::fold_values, and Self::probe
to run more efficiently when there are concurrent calls to the per-thread methods
(Self::with, Self::with_mut, Self::get, Self::set)
by using fine-grained per-thread locking instead of acquiring an object-level write lock.
On the other hand, the per-thread methods may run a bit slower as they require the acquision of the per-thread lock.
§Example
use std::{
sync::Arc,
thread::{self},
time::Duration,
};
use thread_map::ThreadMapX;
const NTHREADS: i32 = 20;
const NITER: i32 = 10;
const SLEEP_MICROS: u64 = 10;
#[test]
fn test() {
main();
}
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}Implementations§
Source§impl<V> ThreadMapX<V>
impl<V> ThreadMapX<V>
Sourcepub fn new(value_init: fn() -> V) -> Self
pub fn new(value_init: fn() -> V) -> Self
Creates a new ThreadMapX instance, with value_init used to create the initial value for each thread.
Sourcepub fn with_mut<W>(&self, f: impl FnOnce(&mut V) -> W) -> W
pub fn with_mut<W>(&self, f: impl FnOnce(&mut V) -> W) -> W
Invokes f mutably on the value associated with the ThreadId of the current thread and returns the invocation result.
If there is no value associated with the current thread then the value_init argument of Self::new is used
to instantiate an initial associated value before f is applied.
§Panics
- If
self’s lock is poisoned.
Examples found in repository?
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}Sourcepub fn with<W>(&self, f: impl FnOnce(&V) -> W) -> W
pub fn with<W>(&self, f: impl FnOnce(&V) -> W) -> W
Invokes f on the value associated with the ThreadId of the current thread and returns the invocation result.
If there is no value associated with the current thread then the value_init argument of Self::new is used
to instantiate an initial associated value before f is applied.
§Panics
- If
self’s lock is poisoned.
Sourcepub fn get(&self) -> Vwhere
V: Clone,
pub fn get(&self) -> Vwhere
V: Clone,
Returns a clone of the value associated with the current thread.
§Panics
- If
self’s lock is poisoned.
Examples found in repository?
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}Sourcepub fn drain(&self) -> Result<HashMap<ThreadId, V>, ThreadMapLockError>
pub fn drain(&self) -> Result<HashMap<ThreadId, V>, ThreadMapLockError>
Returns a HashMap with the values associated with each ThreadId key and clears self’s state.
§Errors
ThreadMapLockErrorif the internal lock is poisoned.
Examples found in repository?
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}Sourcepub fn fold<W>(
&self,
z: W,
f: impl FnMut(W, (ThreadId, &V)) -> W,
) -> Result<W, ThreadMapLockError>
pub fn fold<W>( &self, z: W, f: impl FnMut(W, (ThreadId, &V)) -> W, ) -> Result<W, ThreadMapLockError>
Folds every association in self into an accumulator (with initial value z) by applying an operation f,
returning the final result.
§Errors
ThreadMapLockErrorif the internal lock is poisoned.
Sourcepub fn fold_values<W>(
&self,
z: W,
f: impl FnMut(W, &V) -> W,
) -> Result<W, ThreadMapLockError>
pub fn fold_values<W>( &self, z: W, f: impl FnMut(W, &V) -> W, ) -> Result<W, ThreadMapLockError>
Folds every value in self into an accumulator (with initial value z) by applying an operation f,
returning the final result.
§Errors
ThreadMapLockErrorif the internal lock is poisoned.
Examples found in repository?
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}Sourcepub fn probe(&self) -> Result<HashMap<ThreadId, V>, ThreadMapLockError>where
V: Clone,
pub fn probe(&self) -> Result<HashMap<ThreadId, V>, ThreadMapLockError>where
V: Clone,
Returns a HashMap with clones of the values associated with each ThreadId key at the time the probe
was executed.
§Errors
ThreadMapLockErrorif the internal lock is poisoned.
Examples found in repository?
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
fn main() {
// There is no real need to wrap in `Arc` here because references can be used in scoped threads instead
// of clones, but the `Arc` wrapper would be required if non-scoped threads were used.
let tm: Arc<ThreadMapX<i32>> = Arc::new(ThreadMapX::default());
thread::scope(|s| {
for i in 0..NTHREADS {
let tm = tm.clone();
s.spawn(move || {
for _ in 0..NITER {
thread::sleep(Duration::from_micros(SLEEP_MICROS));
tm.with_mut(move |i0: &mut i32| *i0 += i);
}
let value = tm.get();
assert_eq!(i * NITER, value);
});
}
// Snapshot before thread-local value in main thread is updated.
let probed = tm.probe().unwrap();
println!("probed={probed:?}");
for _ in 0..NITER {
tm.with_mut(move |i0: &mut i32| *i0 += NTHREADS)
}
// Snapshot before all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
});
// Snapshot after all scoped threads terminate.
let probed = tm.probe().unwrap();
println!("\nprobed={probed:?}");
let expected_sum = (0..=NTHREADS).map(|i| i * NITER).sum::<i32>();
let sum = tm.fold_values(0, |z, v| z + v).unwrap();
assert_eq!(expected_sum, sum);
// Extracted values after all scoped threads terminate.
let dumped = tm.drain().unwrap();
println!("\ndumped={dumped:?}");
}