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