Help understanding Rust multithreading

I have a HashMap<&'static str, i32>, I constructed it like this:

let mut to_spy_on: HashMap<&'static str, i32> = HashMap::new();
to_spy_on.insert("a", 1);
to_spy_on.insert("b", 2);
let to_spy_on_box = Arc::new(Mutex::new(to_spy_on));

The values from to_spy_on is changing on an unpredictable interval frequecy. I simulate this kind of changing action like this:

fn change_data_on_background(data: &Arc<Mutex<HashMap<&'static str, i32>>>, key: &'static str) {
    thread::spawn({
        let to_spy_on_box = Arc::clone(&data);
        move || {
            let mut rng = thread_rng();
            let range_for_v = UniformInt::<i32>::new_inclusive(1, 500);
            let range_for_sleep = UniformInt::<u64>::new_inclusive(10, 300);
            for i in 1 .. 100 {
                let rand_v = range_for_v.sample(&mut rng);
                let rand_time = range_for_sleep.sample(&mut rng);
                *to_spy_on_box.lock().unwrap().get_mut(key).unwrap() = rand_v;
                thread::sleep(Duration::from_millis(rand_time));
            }
        }
    });
}

change_data_on_background(&to_spy_on_box, "a");
change_data_on_background(&to_spy_on_box, "b");

What I am trying to do is that as soon as the values from to_spy_on changed, I print the new values.
I tried to realize that as following:

fn spy_on_data(data: &Arc<Mutex<HashMap<&'static str, i32>>>, key: &'static str) {
    thread::spawn({
        let to_spy_on_box = Arc::clone(&data);
        move || {
            let mut last_seen_data = to_spy_on_box.lock().unwrap()[key];
            for i in 1 .. 10_000 {
                let now_seen_data = to_spy_on_box.lock().unwrap()[key];
                if !(last_seen_data == now_seen_data) {
                    println!("the value: {:?}, now changed to {:?}", key, now_seen_data);
                    last_seen_data = now_seen_data;
                }
                thread::sleep(Duration::from_millis(10));
            }
        }
    });
}

spy_on_data(&to_spy_on_box, "a");
spy_on_data(&to_spy_on_box, "b");

This is the playground.

But the function spy_on_data consume too much cpu resources, because it keep running the loop per 10ms although sometimes the update time interval is 300ms.

So is there a way I can avoid that. The mothod I can imaging is like:

fn spy_on_data_callback(data: &Arc<Mutex<HashMap<&'static str, i32>>>, key: &'static str) {
    thread::spawn({
        let to_spy_on_box = Arc::clone(&data);
        move || {
            loop {
                is_the_data_changed? => data[key] {
                    println!(...)
                }
            }
        }
    });
}

How can I do it?

You shouldn't observe values by polling them in a busy loop. You should use some sort of notification mechanism, for example a condition variable.

3 Likes

Typically you might want to use some mechanism for waiting for updates in a blocking manner rather than doing a high frequency of polling. To do this, the standard library offers the type Condvar. You could decide on the desired granularity, either having a single condition variable for the whole map, or having a different one for each key[1]. A single one for all entries is probably fine, unless there’s a lot of independent entries and each thread is only interested in a single one. If you want to also keep the property of your code that only up to one change in 10ms is observable, you can of course still use a sleep before you start waiting on the condition variable again.

Edit: Here’s some example code for the fine-grained approach, because I wanted to try implementing it… however, the thread spying has no way of being shut down yet (other than when the whole program ends), which might be undesired.


  1. though for that more fine-grained approach, there’s the slightly nontrivial exercise of getting the bookkeeping right and storing all the Condvars appropriately; one option might be to use Arc<Condvar>, then it should be possible to put it alongside the entry in the map, and for waiting on it, you’d create a clone of the Arc to avoid borrow checking problems ↩︎

1 Like

I found a way to shut down the thread spying by adding a flag variable on the loop. This is the playground.

fn spy_on_data(data: &Arc<Mutex<HashMap<&'static str, (i32, Arc<Condvar>, bool)>>>, key: &'static str) {
    thread::spawn({
        let to_spy_on_box = Arc::clone(&data);
        move || {
            let mut guard = to_spy_on_box.lock().unwrap();
            let cv = guard[key].1.clone();
            loop {
                guard = cv.wait(guard).unwrap();
                println!("the value: {:?}, now changed to {:?}", key, guard[key].0);
                if !guard[key].2 {
                    println!("the spy on {:?} exit", key);
                    break
                }
            }
        }
    });
}

fn terminate_spy(data: &Arc<Mutex<HashMap<&'static str, (i32, Arc<Condvar>, bool)>>>, key: &'static str) {
    data.lock().unwrap().get_mut(key).unwrap().2 = false;
}
fn terminate_spy(data: &Arc<Mutex<HashMap<&'static str, (i32, Arc<Condvar>, bool)>>>, key: &'static str) >{
    data.lock().unwrap().get_mut(key).unwrap().2 = false;
}

for this to work reliably, you probably also want to notify the condvar after this change.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.