wb9688
wb9688

Reputation: 189

How do I modify a value in one thread and read the value in another thread using shared memory?

The following Python code creates a thread (actually a process) with an array containing two floats passed to it, the thread counts up 1 by the first float and -1 by the second float every 5 seconds, while the main thread is continuously printing the two floats:

from multiprocessing import Process, Array
from time import sleep

def target(states):
    while True:
        states[0] -= 1
        states[1] += 1
        sleep(5)

def main():
    states = Array("d", [0.0, 0.0])
    process = Process(target=target, args=(states,))
    process.start()
    while True:
        print(states[0])
        print(states[1])

if __name__ == "__main__":
    main()

How can I do the same thing using shared memory in Rust? I've tried doing the following (playground):

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let data = data.clone();
    thread::spawn(move || {
        let mut data = data.lock().unwrap();
        data[0] = 1.0;
    });
    print!("{}", data[0]);
}

But that's giving a compile error:

error: cannot index a value of type `std::sync::Arc<std::sync::Mutex<[_; 1]>>`
  --> <anon>:12:18
   |>
12 |>     print!("{}", data[0]);
   |>                  ^^^^^^^

And even if that'd work, it does something different. I've read this, but I've still no idea how to do it.

Upvotes: 3

Views: 2365

Answers (3)

Puneet
Puneet

Reputation: 113

If you update common data by a thread, the other threads might not see the updated value, unless you do the following:

  1. Declare the variable as volatile which makes sure that the latest update is given back to the threads that read the variable. The data is read from the memory block but not from cache.

  2. Make all updates and reads as synchronized which might turn out to be costly in terms of performance but is sure to deal with data corruptions/in-consistency due to non-synchronization methods of writes and reads by distinct threads.

Upvotes: -3

Arjan
Arjan

Reputation: 21485

Ok, so let's first fix the compiler error:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let thread_data = data.clone();
    thread::spawn(move || {
        let mut data = thread_data.lock().unwrap();
        data[0] = 1.0;
    });
    println!("{}", data.lock().unwrap()[0]);
}

The variable thread_data is always moved into the thread, that is why it cannot be accessed after the thread is spawned. But this still has a problem: you are starting a thread that will run concurrently with the main thread and the last print statement will execute before the thread changes the value most of the time (it will be random).

To fix this you have to wait for the thread to finish before printing the value:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let thread_data = data.clone();
    let t = thread::spawn(move || {
        let mut data = thread_data.lock().unwrap();
        data[0] = 1.0;
    });
    t.join().unwrap();
    println!("{}", data.lock().unwrap()[0]);
}

This will always produce the correct result.

Upvotes: 1

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88956

Your code is not that far off! :)

Let's look at the compiler error first: it says that you are apparently attempting to index something. This is true, you want to index the data variable (with data[0]), but the compiler complains that the value you want to index is of type std::sync::Arc<std::sync::Mutex<[_; 1]>> and cannot be indexed.

If you look at the type, you can quickly see: my array is still wrapped in a Mutex<T> which is wrapped in an Arc<T>. This brings us to the solution: you have to lock for read access, too. So you have to add the lock().unwrap() like in the other thread:

print!("{}", data.lock().unwrap()[0]);

But now a new compiler error arises: use of moved value: `data`. Dang! This comes from your name shadowing. You say let data = data.clone(); before starting the thread; this shadows the original data. So how about we replace it by let data_for_thread = data.clone() and use data_for_thread in the other thread? You can see the working result here on the playground.


Making it do the same thing as the Python example isn't that hard anymore then, is it?

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

let data = Arc::new(Mutex::new([0.0, 0.0]));
let data_for_thread = data.clone();
thread::spawn(move || {
    loop {
        thread::sleep(Duration::from_secs(5))
        let mut data = data_for_thread.lock().unwrap();
        data[0] += 1.0;
        data[1] -= 1.0;
    }
});

loop {
    let data = data.lock().unwrap();
    println!("{}, {}", data[0], data[1]);
}

You can try it here on the playground, although I changed a few minor things to allow running on the playground.

Upvotes: 4

Related Questions