Is it possible to have Read only HashMap across threads without Mutex?


#1

Hi "Rusters"
I have a multithread application where one of threads adding and deleting elements from HashMap and other few threads only reading from that map, so by logic it is guaranteed that other maps don’t have a writable access to that map.
I can implement this with Arc<Mutex<HashMap<String, SomeData>>>, but in this case if some of the threads starting reading process, others would be waiting until Mutex will be reseted. I want to have a concurrent Reads and only one write access like RwLock but for multiple threads.
In C++ I can actually just refer using pointer and make sure from logic that threads have read only access.

How can I implement this with Rust ? I couldn’t find any relevant information about this problem in documentation. RwLock is fine but it’s not working with multiple threads.

Thanks


#2

You’ll need to put the RwLock in an Arc to share access across threads: Arc<RwLock<HashMap<K, V>>>.


#3

Wow! thanks, its worked.
Also what does mean Sized here RwLock<T: ?Sized> ? Should I implement that trait for my structure in order to use it with RwLock ?


#4

The ?Sized bound actually makes RwLock able to accept more types than it would otherwise. For example, you could put a trait object inside a RwLock. By default, generic parameters have a Sized bound, and adding T: ?Sized removes that.

For more info, I recommend reading @huon’s blog post discussing the Sized trait.


#5

I realized I didn’t actually answer your question. You should be able to just stick your data in the RwLock without any extra implementations.


#6

Ok got it, thanks!
Rust getting better in my eyes over the time :slight_smile:


#7

You don’t need the RwLock if you only need read-only access. A plain Arc<HashMap<K, V>> is enough.


#8

But according to the OP, one thread is supposed to write to it, so it needs to be in some kind of internal mutability wrapper.


#9

Hey, if you indeed statically now that readers and the writer do not exist simultaneously, then you can probably use crossbeam crate and avoid any explicit synchronization and interior mutability:

extern crate crossbeam;

fn main() {
    let mut data = vec![];
    loop {
        crossbeam::scope(|scope| {
            scope.spawn(|| {
                writer(&mut data)
            })
        });

        crossbeam::scope(|scope| {
            for _ in 0..3 {
                scope.spawn(|| {
                    reader(&data)
                });
            }
        });
    }
}

fn writer(data: &mut Vec<i32>) {
    data.push(92)
}

fn reader(data: &Vec<i32>) {
    println!("{}", data.iter().cloned().sum::<i32>());
}

playground: http://play.integer32.com/?gist=7d7565b0b333bb866e27f7da3c95f8cb&version=stable


#10

But note that the first scope has no advantage over writer(&mut data) in the main thread unless you spawn other threads in it.


#11

Loved the idea of https://github.com/aturon/crossbeam , it would be very useful
So basically my use-case is something like this

fn main() {
    let mut data = vec![];
    crossbeam::scope(|scope| {
           for _ in 0..3 {
                scope.spawn(|| {
                    reader(&data)
                });
            }
          
            scope.spawn(|| {
                writer(&mut data)
            })
    });
}

fn writer(data: &mut Vec<i32>) {
    loop {
         data.push(92);
         thread::sleep(Duration::from_millis(4000));
    }
}

fn reader(data: &Vec<i32>) {
    loop {
         println!("{}", data.iter().cloned().sum::<i32>());
         thread::sleep(Duration::from_millis(1000));
   }
}

#12

Unfortunately that won’t work: A thread holding an &mut T reference cannot exist at the same time as a thread holding an &T reference to the same data. So you either need separate scopes like in @matklad’s example (which forces the writing thread to exit before the reading threads start), or an RwLock to synchronize the threads.