无法在生成的线程中调用函数,因为它"无法满足所需的生命周期"

Bog*_*kiy 1 multithreading closures lifetime rust

我可以运行这段代码

fn testf(host: &str) {}

fn start(host: &str) {
    testf(host);
    testf(host);
}
Run Code Online (Sandbox Code Playgroud)

但出于某种原因,我无法运行这个:

fn testf(host: &str) {}

fn start(host: &str) {
    thread::spawn(move || testf(host));
    thread::spawn(move || testf(host));
}
Run Code Online (Sandbox Code Playgroud)

因为以下错误

src/server.rs:30:5: 30:18 error: the type `[closure@src/server.rs:30:19: 30:38 host:&str]` does not fulfill the required lifetime
src/server.rs:30     thread::spawn(move || testf(host));
                     ^~~~~~~~~~~~~
note: type must outlive the static lifetime
error: aborting due to previous error
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下,它有什么问题以及如何解决它?

Vla*_*eev 6

您的闭包捕获字符串切片,因此其环境的生命周期不会超过此切片的生命周期,但thread::spawn()要求其参数具有静态生存期:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
    where F: FnOnce() -> T, 
          F: Send + 'static, 
          T: Send + 'static
Run Code Online (Sandbox Code Playgroud)

(注意F: 'static要求)

这是必要的,因为当生成的线程thread::spawn()运行时,可能已经销毁了从中获取切片的字符串.Rust实际上阻止了代码中的错误!

有几种方法可以解决它.

1)最简单的方法是克隆每个线程的字符串:

fn start(host: &str) {
    {
        let host = host.to_owned();
        thread::spawn(move || testf(&host));
    }
    {
        let host = host.to_owned();
        thread::spawn(move || testf(&host));
    }
}
Run Code Online (Sandbox Code Playgroud)

这样每个线程都会收到自己的字符串副本,当线程本身完成时,该副本将被销毁.

2)如果你知道之前,你的线程应该完成start()函数结束时,您可以使用第三方librariy像横梁,以引用传递到产生的线程:

extern crate crossbeam;

fn start(host: &str) {
    crossbeam::scope(|scope| {
        scope.spawn(move || testf(host));
        scope.spawn(move || testf(host));
    });
}
Run Code Online (Sandbox Code Playgroud)

这种方式start()将等待,直到两个线程生成scoped()完成后才返回,确保任何字符串host指向不会过早地被销毁.

以前这样的功能包含在标准库中,但是它的实现方式被发现是不合理的,所以它被弃用了; 正确替换此功能尚未添加回标准库.

3)甚至另一种选择是使用Arc<String>在线程之间共享字符串,但这需要在以下之外进行更多重大更改start():

use std::sync::Arc;

fn start(host: Arc<String>) {
    {
        let host = host.clone();
        thread::spawn(move || testf(&host));
    }
    {
        let host = host.clone();
        thread::spawn(move || testf(&host));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这种方法,你需要将你的字符串保持在一个Arc(这是一个"原子引用计数"指针),所以这要求你改变调用的代码start().克隆可能更好.当然,如果你想分享没有&str,但&SomeStruct这里SomeStruct是大和/或不可复制的,有没有办法避免作用域异Arc.