Is it possible to share data with threads without

2020-07-08 06:34发布

问题:

When I'm delegating work to threads I often have a piece of data that will outlive all of the threads, such as numbers in the following example:

use std::thread;

fn main() {
    let numbers = vec![1, 2, 3];

    let thread_a = thread::spawn(|| println!("{}", numbers.len()));
    let thread_b = thread::spawn(|| println!("{}", numbers.len()));

    thread_a.join().unwrap();
    thread_b.join().unwrap();
}

It's not modified anywhere, and because of the joins, it's guaranteed that the threads are done using it. However, Rust's borrow checker is not able to tell:

error[E0373]: closure may outlive the current function, but it borrows `numbers`, which is owned by the current function
 --> src/main.rs:6:34
  |
6 |     let thread_a = thread::spawn(|| println!("{}", numbers.len()));
  |                                  ^^                ------- `numbers` is borrowed here
  |                                  |
  |                                  may outlive borrowed value `numbers`
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:6:20
  |
6 |     let thread_a = thread::spawn(|| println!("{}", numbers.len()));
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `numbers` (and any other referenced variables), use the `move` keyword
  |
6 |     let thread_a = thread::spawn(move || println!("{}", numbers.len()));
  |                                  ^^^^^^^

The solutions I've seen so far all involve cloning the piece of data (or cloning an Arc of the data). Is it possible to do it without any cloning, though?

回答1:

You might have the wrong idea: cloning an Arc is just incrementing a reference counter and making a copy of a pointer; it doesn't perform any additional allocation. Of course, creating the Arc involves an allocation, but then, you're already allocating in order to construct the Vec, so one additional fixed-size allocation isn't likely to hurt.

If all you really need is the length, you can just compute that outside the thread's closure and store it in a variable; a usize has no problems crossing a thread boundary.

The issue is that the compiler is unable to infer from the use of join() that a given thread is bound to a limited lifetime... it doesn't even try.

Before Rust 1.0, there was a thread::scoped constructor that allowed you to pass in non-'static references, but that had to be de-stabilised due to a memory safety issue. See How can I pass a reference to a stack variable to a thread? for alternatives.