是否可以在线程之间共享HashMap而不锁定整个HashMap?

时间:2018-05-10 22:54:30

标签: multithreading rust

我想在线程之间有一个共享结构。该结构有许多永远不会被修改的字段和一个HashMap,它是。我不想锁定整个HashMap以进行单次更新/删除,因此我的HashMap看起来像HashMap<u8, Mutex<u8>>。这是有效的,但没有任何意义,因为线程无论如何都会锁定整个地图。

这是这个工作版本,没有线程;我不认为这个例子是必要的。

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

fn main() {
    let s = Arc::new(Mutex::new(S::new()));
    let z = s.clone();
    let _ = z.lock().unwrap();
}

struct S {
    x: HashMap<u8, Mutex<u8>>, // other non-mutable fields
}

impl S {
    pub fn new() -> S {
        S {
            x: HashMap::default(),
        }
    }
}

Playground

这有可能吗?我在文档中遗漏了一些明显的东西吗?

我一直在尝试让这个工作,但我不知道如何。基本上我看到的每个例子总是有一个Mutex(或RwLock,或类似的东西)来保护内在价值。

2 个答案:

答案 0 :(得分:1)

也许您想考虑使用rust-evmap

  

无锁,最终一致的并发多值映射。

需要权衡的因素是最终的一致性:直到作者刷新地图,读者才能看到更改。刷新是原子的,作者决定何时进行刷新,并将新数据展示给读者。

答案 1 :(得分:0)

假设数据键可映射到u8

您可以拥有Arc<HashMap<u8,Mutex<HashMap<Key,Value>>>

初始化数据结构时,先将所有第一级贴图填充到Arc中(初始化后将不可变)

当您想要从地图中获取一个值时,您将需要进行两次获取,例如:

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

unwrap是安全的,因为我们使用所有值初始化了第一个映射。

在地图上写类似的内容:

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

很容易看到争用减少了,因为我们现在有256个Mutex,并且多个线程询问同一个Mutex的可能性很低。

@Shepmaster示例具有100个线程在我的计算机上花费大约10s,下面的示例花费了1秒多一点的时间。

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<HashMap<u8,Mutex<HashMap<u8,Mutex<i32>>>>> ) {
    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
    }
}