限制生存期的线程

到目前为止我们讨论的所有生命周期问题都有一个共同的根源:生成的线程可能比其父线程寿命更长。我们可以通过使用**限制生存期的线程(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被丢弃前完成,因此不存在悬空引用的风险。

编译器不会报错!