Is it possible to share a HashMap between threads without locking the entire HashMap?

前端 未结 3 1380
时光说笑
时光说笑 2021-02-13 05:23

I would like to have a shared struct between threads. The struct has many fields that are never modified and a HashMap, which is. I don\'t want to lock the whole

3条回答
  •  -上瘾入骨i
    2021-02-13 06:11

    Suppose the key of the data is map-able to a u8

    You can have Arc>>

    When you initialize the data structure you populate all the first level map before putting it in Arc (it will be immutable after initialization)

    When you want a value from the map you will need to do a double get, something like:

    data.get(&map_to_u8(&key)).unwrap().lock().expect("poison").get(&key)

    where the unwrap is safe because we initialized the first map with all the value.

    to write in the map something like:

    data.get(&map_to_u8(id)).unwrap().lock().expect("poison").entry(id).or_insert_with(|| value);

    It's easy to see contention is reduced because we now have 256 Mutex and the probability of multiple threads asking the same Mutex is low.

    @Shepmaster example with 100 threads takes about 10s on my machine, the following example takes a little more than 1 second.

    use std::{
        collections::HashMap,
        sync::{Arc, Mutex, RwLock},
        thread,
        time::Duration,
    };
    
    fn main() {
        let mut inner = HashMap::new( );
        for i in 0..=u8::max_value() {
            inner.insert(i, Mutex::new(HashMap::new()));
        }
        let data = Arc::new(inner);
    
        let threads: Vec<_> = (0..100)
            .map(|i| {
                let data = Arc::clone(&data);
                thread::spawn(move || worker_thread(i, data))
            })
            .collect();
    
        for t in threads {
            t.join().expect("Thread panicked");
        }
    
        println!("{:?}", data);
    }
    
    fn worker_thread(id: u8, data: Arc>>>> ) {
        loop {
    
            // first unwrap is safe to unwrap because we populated for every `u8`
            if let Some(element) = data.get(&id).unwrap().lock().expect("poison").get(&id) {
                let mut element = element.lock().expect("Mutex poisoned");
    
                // Perform our normal work updating a specific element.
                // The entire HashMap only has a read lock, which
                // means that other threads can access it.
                *element += 1;
                thread::sleep(Duration::from_secs(1));
    
                return;
            }
    
            // If we got this far, the element doesn't exist
    
            // Get rid of our read lock and switch to a write lock
            // You want to minimize the time we hold the writer lock
    
            // We use HashMap::entry to handle the case where another thread
            // inserted the same key while where were unlocked.
            thread::sleep(Duration::from_millis(50));
            data.get(&id).unwrap().lock().expect("poison").entry(id).or_insert_with(|| Mutex::new(0));
            // Let the loop start us over to try again
        }
    }
    
    

提交回复
热议问题