Search code examples
rust

rust: using hashmap arc and rwlock across multiple threads


Trying to wrap my head around how to insert things to a hasmap from different threads, but still couldn't get it.

Why is the following program on every execution missing some keys? My current understanding is that .write wait to acquire the lock and .join wait for all threads to finish?

Thus I would expect to eventually all threads to insert their values into the hashmap, but obviously I am still missing something.

playground

use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use std::thread;
use std::vec;

pub fn main() {
    let mut contacts: HashMap<String, String> = HashMap::new();
    contacts.insert("main-thread".to_owned(), "hello world".to_owned());

    let contacts = Arc::new(RwLock::new(contacts));
    let mut children = vec![];
    for _ in 0..10 {
        let lock_contacts = Arc::clone(&contacts);
        children.push(thread::spawn(move || {
            let num = thread::current().id();
            let num = format!("{num:?}");
            if let Ok(mut contacts) = lock_contacts.write() {
                contacts.insert(num.clone(), "hey".to_owned());
            }
        }));
    }
    let _ = children.into_iter().map(|c| c.join());
    dbg!(contacts);
}

Solution

  • You are not actually joining threads before exiting from the main. And your culprit is this line.

    let _ = children.into_iter().map(|c| c.join());
    

    This line does nothing. There is no iteration, and that is because iterators in rust are lazy. If you would not discard value returned from the map, you would get a following warning.

    warning: unused `Map` that must be used
      --> src/main.rs:23:5
       |
    23 |     children.into_iter().map(|c| c.join());
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: iterators are lazy and do nothing unless consumed
       = note: `#[warn(unused_must_use)]` on by default
    help: use `let _ = ...` to ignore the resulting value
       |
    23 |     let _ = children.into_iter().map(|c| c.join());
       |     +++++++
    

    Note this part: iterators are lazy and do nothing unless consumed.

    The solution is not to ignore this warning, but to instead iterate over the values and actually join threads. You can do it either by using a consuming iterator-adaptor like Iterator::for_each:

    children.into_iter().for_each(|c| c.join().unwrap());
    

    or simply using a for loop:

    for child in children {
        child.join().unwrap()
    }