multithreadingvectorrustmutable

Change elements in vector using multithreading in Rust


I'm new in Rust and i'm trying to allocate computational work to threads.

I have vector of strings, i would want to create to each string one thread to do his job. There's simple code:

use std::thread;

fn child_job(s: &mut String) {
    *s = s.to_uppercase();
}

fn main() {
    // initialize
    let mut thread_handles = vec![];
    let mut strings = vec![
        "hello".to_string(),
        "world".to_string(),
        "testing".to_string(),
        "good enough".to_string(),
    ];

    // create threads
    for s in &mut strings {
        thread_handles.push(thread::spawn(|| child_job(s)));
    }

    // wait for threads
    for handle in thread_handles {
        handle.join().unwrap();
    }

    // print result
    for s in strings {
        println!("{}", s);
    }
}

I got errors while compile:

error[E0597]: `strings` does not live long enough
  --> src/main.rs:18:14
   |
18 |     for s in &mut strings {
   |              ^^^^^^^^^^^^
   |              |
   |              borrowed value does not live long enough
   |              argument requires that `strings` is borrowed for `'static`
...
31 | }
   | - `strings` dropped here while still borrowed

error[E0505]: cannot move out of `strings` because it is borrowed
  --> src/main.rs:28:14
   |
18 |     for s in &mut strings {
   |              ------------
   |              |
   |              borrow of `strings` occurs here
   |              argument requires that `strings` is borrowed for `'static`
...
28 |     for s in strings {
   |              ^^^^^^^ move out of `strings` occurs here

I can't understand what's wrong with lifetime of pointers and how i should fix that. For me it looks OK, because every thread get only one mutable pointer of string and doesn't affect the vector itself in any way.


Solution

  • Caesar's answer shows how to solve the problem using crossbeam's scoped threads. If you don't want to depend on crossbeam, then the approach of wrapping the values in Arc<Mutex<T>>, as shown in tedtanner's answer, is a reasonable general strategy.

    However in this case the mutex is really unnecessary because the threads don't share the strings, either with each other or with the main thread. Locking is an artifact of using Arc, which is itself mandated by the static lifetime rather than a need for sharing. Although the locks are uncontended, they do add some overhead and are best avoided. In this case we can avoid both Arc and Mutex by moving each string to its respective thread, and retrieving the modified string once the threads finish.

    This modification compiles and runs using just the standard library and safe code, and without requiring Arc or Mutex:

    // ... child_job defined as in the question ...
    
    fn main() {
        let strings = vec![
            "hello".to_string(),
            "world".to_string(),
            "testing".to_string(),
            "good enough".to_string(),
        ];
    
        // start the threads, giving them the strings
        let mut thread_handles = vec![];
        for mut s in strings {
            thread_handles.push(thread::spawn(move || {
                child_job(&mut s);
                s
            }));
        }
    
        // wait for threads and re-populate `strings`
        let strings = thread_handles.into_iter().map(|h| h.join().unwrap());
    
        // print result
        for s in strings {
            println!("{}", s);
        }
    }
    

    Playground