berkes
berkes

Reputation: 27553

How do I access shared memory mutable and immutable in separate threads

I have a multithreaded program that wants to access resources across threads. Some want to write to them, some want to read from them.

I'm not sure if this counts as a global mutable singleton, because my setup isn't global, but the solution might be similar?

A very much simplified version would be the code below.

What it tries to do, is have one thread write to a struct, and another read from this same struct. So when thread A mutates the data, thread B would read the mutated data then.

use std::{thread, time::Duration};

struct TagList {
  list: Vec<String>
}

impl TagList {
  fn add(self: &mut TagList, tag: String) {
    self.list.push(tag);
  }

  fn read(&self) -> Vec<String> {
    self.list.clone()
  }
}

fn main() {
    let mut list = TagList { list: vec![] };

    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         list.add(tag.to_string());
         thread::sleep(Duration::from_millis(100));
       });
    });

    thread::spawn(move || {
      loop {
        dbg!(list.read());
        thread::sleep(Duration::from_millis(20));
      }
    });
}

This, rather obviously, fails with a borrow error:

error[E0382]: use of moved value: `list`
  --> src/main.rs:79:19
   |
70 |     let mut list = TagList { list: vec![] };
   |         -------- move occurs because `list` has type `TagList`, which does not implement the `Copy` trait
71 | 
72 |     thread::spawn(move || {
   |                   ------- value moved into closure here
73 |        ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
74 |          list.add(tag.to_string());
   |          ---- variable moved due to use in closure
...
79 |     thread::spawn(move || {
   |                   ^^^^^^^ value used here after move
80 |       dbg!(list.read());
   |            ---- use occurs due to use in closure

I've tried to solve this by wrapping the list in an Arc:

use std::sync::Arc;
// ...

    let list = Arc::new(TagList { list: vec![] });
    let write_list = Arc::get_mut(&mut list).unwrap();
    let read_list = Arc::clone(&list);

    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         write_list.add(tag.to_string());
         thread::sleep(Duration::from_millis(100));
       });
    });

    thread::spawn(move || {
      loop {
        dbg!(read_list.read());
        thread::sleep(Duration::from_millis(20));
      }
    });

This fails because I probably don't understand how Arc is supposed to work or how it relates to lifetimes:

error[E0597]: `list` does not live long enough
  --> src/main.rs:71:35
   |
71 |     let write_list = Arc::get_mut(&mut list).unwrap();
   |                      -------------^^^^^^^^^-
   |                      |            |
   |                      |            borrowed value does not live long enough
   |                      argument requires that `list` is borrowed for `'static`
...
85 | }
   | - `list` dropped here while still borrowed

error[E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable
  --> src/main.rs:72:32
   |
71 |     let write_list = Arc::get_mut(&mut list).unwrap();
   |                      -----------------------
   |                      |            |
   |                      |            mutable borrow occurs here
   |                      argument requires that `list` is borrowed for `'static`
72 |     let read_list = Arc::clone(&list);
   |                                ^^^^^ immutable borrow occurs here

Is what I want possible at all? (I'm pretty sure i've seen this used with e.g. std::sync::mpsc where somehow messages are pushed and read over threads). What should I use? Is Arc the proper structure for this, or am I looking at the wrong solution there? What should I read up on to understand how such problems are typically solved in Rust?

Upvotes: 1

Views: 465

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70910

Arc does not allow mutation, and Arc::get_mut() is not a solution to that. It allows mutation when the Arc has only one instance (thus the second error) and return a reference which is not 'static, therefore you cannot move it into a thread (the first error).

If you need to mutate the content of an Arc, use Mutex or RwLock.

Upvotes: 1

Related Questions