限制生存期的线程

到目前为止我们讨论的所有生命周期问题都有一个共同的根源:生成的线程可能比其父线程寿命更长。我们可以通过使用**限制生存期的线程(scoped threads)**来绕过这个问题。

#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
let midpoint = v.len() / 2;

std::thread::scope(|scope| {
    scope.spawn(|| {
        let first = &v[..midpoint];
        println!("Here's the first half of v: {first:?}");
    });
    scope.spawn(|| {
        let second = &v[midpoint..];
        println!("Here's the second half of v: {second:?}");
    });
});

println!("Here's v: {v:?}");
}

下面我们来详细解析这个过程。

scope

std::thread::scope函数创建一个新的作用域。它接收一个闭包作为输入,该闭包有一个参数:一个Scope实例。

限制生存期的生成

Scope提供了一个spawn方法。与std::thread::spawn不同,使用Scope生成的所有线程会在作用域结束时自动加入(即等待线程完成)。

如果我们把前面的例子用std::thread::spawn重写,看起来会是这样的:

#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
let midpoint = v.len() / 2;

let handle1 = std::thread::spawn(|| {
    let first = &v[..midpoint];
    println!("Here's the first half of v: {first:?}");
});
let handle2 = std::thread::spawn(|| {
    let second = &v[midpoint..];
    println!("Here's the second half of v: {second:?}");
});

handle1.join().unwrap();
handle2.join().unwrap();

println!("Here's v: {v:?}");
}

从环境借用

然而,这个转换后的例子无法编译:编译器会抱怨说,由于&v的生命周期不是'static,所以不能在生成的线程中使用它。

但这在使用std::thread::scope时不是问题——你可以安全地从环境借用

在我们的例子中,v在生成线程之前创建。它只会在scope返回后被丢弃。同时,所有在scope内部生成的线程都保证了在v被丢弃前完成,因此不存在悬空引用的风险。

编译器不会报错!