Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rust: using hashmap arc and rwlock across multiple threads

Tags:

rust

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);
}
like image 773
Cesc Avatar asked Aug 08 '26 14:08

Cesc


2 Answers

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()
}
like image 89
Aleksander Krauze Avatar answered Aug 12 '26 06:08

Aleksander Krauze


Your mistake was in this line:

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

Recall from the Rust book that iterators are lazy. Iterator::map merely creates an adapter, but does not actually evaluate the values. Therefore your code is not actually synchronized.

To fix this, you can either:

  1. evaluate the iterator, probably using Iterator::collect;
  2. or use a simple for loop instead.
like image 32
cyqsimon Avatar answered Aug 12 '26 07:08

cyqsimon