Struct ThreadMapX

Source
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>

Source

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.

Source

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?
examples/doc_thread_map_x.rs (line 28)
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:?}");
}
Source

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.
Source

pub fn get(&self) -> V
where V: Clone,

Returns a clone of the value associated with the current thread.

§Panics
  • If self’s lock is poisoned.
Examples found in repository?
examples/doc_thread_map_x.rs (line 30)
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:?}");
}
Source

pub fn set(&self, v: V)

§Panics
  • If self’s lock is poisoned.
Source

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
Examples found in repository?
examples/doc_thread_map_x.rs (line 57)
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:?}");
}
Source

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
Source

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
Examples found in repository?
examples/doc_thread_map_x.rs (line 53)
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:?}");
}
Source

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
Examples found in repository?
examples/doc_thread_map_x.rs (line 36)
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:?}");
}

Trait Implementations§

Source§

impl<V: Debug> Debug for ThreadMapX<V>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<V: Default> Default for ThreadMapX<V>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<V> !Freeze for ThreadMapX<V>

§

impl<V> RefUnwindSafe for ThreadMapX<V>

§

impl<V> Send for ThreadMapX<V>
where V: Send,

§

impl<V> Sync for ThreadMapX<V>
where V: Send,

§

impl<V> Unpin for ThreadMapX<V>
where V: Unpin,

§

impl<V> UnwindSafe for ThreadMapX<V>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.