【问题标题】:Issue with borrowing while spawning thread inside other thread在其他线程中生成线程时借用问题
【发布时间】:2021-03-24 06:31:45
【问题描述】:

我已阅读以下问题:

How can I run a set of functions on a recurring interval without running the same function at the same time using only the standard Rust library?

并详细阐述了一些更复杂的测试。 以下代码为函数添加了一个 &str 参数,它可以工作:

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

fn main() {
    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        let one: &str = "Alpha";
        let two: &str = "Beta";

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

我的理解是这行得通,因为 Copy Trait 是为 str 实现的。 现在考虑以下示例:

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

fn main() {
    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        let one: String = String::from("Alpha");
        let two: String = String::from("Beta");

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

我在编译时得到这个:

error[E0382]: use of moved value: `one`
  --> src\main.rs:19:42
   |
10 |         let one: String = String::from("Alpha");
   |             --- move occurs because `one` has type `String`, which does not implement the `Copy` trait
...
19 |             let thread_a = thread::spawn(move || { a(&one) });
   |                                          ^^^^^^^      --- use occurs due to use in closure
   |                                          |
   |                                          value moved into closure here, in previous iteration of loop

error[E0382]: use of moved value: `two`
  --> src\main.rs:20:42
   |
11 |         let two: String = String::from("Beta");
   |             --- move occurs because `two` has type `String`, which does not implement the `Copy` trait
...
20 |             let thread_b = thread::spawn(move || { b(&two) });
   |                                          ^^^^^^^      --- use occurs due to use in closure
   |                                          |
   |                                          value moved into closure here, in previous iteration of loop

error: aborting due to 2 previous errors

EDIT1

似乎可以使用 .clone() 解决 但现在考虑以下代码:

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

fn main() {

    let one: String = String::from("Alpha");
    let two: String = String::from("Beta");

    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let one = one.clone();
            let two = two.clone();

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

我现在收到不同的错误代码:

error[E0373]: closure may outlive the current function, but it borrows `two`, which is owned by the current function
  --> src\main.rs:11:35
   |
11 |     let scheduler = thread::spawn(|| {
   |                                   ^^ may outlive borrowed value `two`
...
21 |             let two = two.clone();
   |                       --- `two` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src\main.rs:11:21
   |
11 |       let scheduler = thread::spawn(|| {
   |  _____________________^
12 | |         let wait_time = Duration::from_millis(500);
13 | |
14 | |         // Make this an infinite loop
...  |
38 | |         }
39 | |     });
   | |______^
help: to force the closure to take ownership of `two` (and any other referenced variables), use the `move` keyword
   |
11 |     let scheduler = thread::spawn(move || {
   |                                   ^^^^^^^

error[E0373]: closure may outlive the current function, but it borrows `one`, which is owned by the current function
  --> src\main.rs:11:35
   |
11 |     let scheduler = thread::spawn(|| {
   |                                   ^^ may outlive borrowed value `one`
...
20 |             let one = one.clone();
   |                       --- `one` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src\main.rs:11:21
   |
11 |       let scheduler = thread::spawn(|| {
   |  _____________________^
12 | |         let wait_time = Duration::from_millis(500);
13 | |
14 | |         // Make this an infinite loop
...  |
38 | |         }
39 | |     });
   | |______^
help: to force the closure to take ownership of `one` (and any other referenced variables), use the `move` keyword
   |
11 |     let scheduler = thread::spawn(move || {
   |                                   ^^^^^^^

error: aborting due to 2 previous errorsù

【问题讨论】:

    标签: multithreading rust closures borrowing


    【解决方案1】:

    为简洁起见,我只提到one,但同样适用于twothread::spawn(move || { a(&one) }) 的问题是 one 被移动到闭包中,然后导致编译错误,因为 one 不再可用于下一次迭代。

    预先借用&one 也不起作用,因为借用one 的线程可以比外线程更长寿。要使其正常工作,您可以在生成线程之前克隆 one(和 two)。

    let one = one.clone();
    let two = two.clone();
    
    let thread_a = thread::spawn(move || a(&one));
    let thread_b = thread::spawn(move || b(&two));
    

    或者,如果你真的想借它,而不是克隆它。然后你可以使用,例如crossbeam 和生成线程的范围。 另见"How can I pass a reference to a stack variable to a thread?"

    ...
    
    let one: String = String::from("Alpha");
    let two: String = String::from("Beta");
    
    let one = &one;
    let two = &two;
    
    crossbeam::scope(|scope| {
        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);
    
            let thread_a = scope.spawn(move |_| a(&one));
            let thread_b = scope.spawn(move |_| b(&two));
    
            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");
    
            let runtime = start.elapsed();
    
            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    })
    .unwrap();
    

    【讨论】:

    • 谢谢,非常有趣。我添加了一个不同的版本,现在我收到了不同的错误消息。在这种情况下我应该如何解决它?
    • 这个问题可以通过在初始关闭处添加move来解决。
    【解决方案2】:

    如果您在多个线程之间共享非静态不可变数据,请使用Arc。这就是它的用途。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-21
      • 2013-10-13
      • 1970-01-01
      • 1970-01-01
      • 2018-03-08
      • 2010-12-26
      相关资源
      最近更新 更多