69、作用域线程(Scoped threads)

作用域线程

到目前为止,我们讨论过的所有生命周期问题都有一个共同的根源:生成的线程的生命周期可能超过其父线程。我们可以通过使用作用域线程来避免这个问题。

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:?}");

让我们来看看发生了什么

作用域

std::thread::scope 函数创建一个新的作用域。std::thread::scope 将闭包作为输入,并带有一个参数:Scope 实例。

 

Scoped spawns

Scope 公开了一个 spawn 方法。与 std::thread::spawn 不同,使用 Scope 生成的所有线程都会在作用域结束时自动join

如果我们把前面的例子 "翻译 "成 std::thread::spawn,就会像这样:

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 内部生成的线程都保证会在 scope 返回之前完成执行。因此,不存在悬空引用的风险。

编译器不会报错!

 

练习

// TODO: Given a vector of integers, split it in two halves
//  and compute the sum of each half in a separate thread.
//  Don't perform any heap allocation. Don't leak any memory.
// TODO: 给定一个整数向量,将其分成两半
// 并在一个单独的线程中计算每一半的和。
// 不要进行任何堆分配。不泄漏任何内存。

pub fn sum(v: Vec<i32>) -> i32 {
    let v=v.leak();
    let mid = v.len()/2;
    let (v1,v2)=v.split_at(mid);
    std::thread::scope(|scope|{
        scope.spawn(||{
            let mut sum=0;
            for i in v1 {
                sum += i;
            }
            sum
        }).join().unwrap() + scope.spawn(||{
            let mut sum=0;
            for i in v2 {
                sum+=i;
            }
            sum
        }).join().unwrap()
    })
}

 

官方答案:

pub fn sum(v: Vec<i32>) -> i32 {
    let mid = v.len() / 2;
    let (left, right) = v.split_at(mid);

    std::thread::scope(|s| {
        let left = s.spawn(|| left.iter().sum::<i32>());
        let right = s.spawn(|| right.iter().sum::<i32>());
        left.join().unwrap() + right.join().unwrap()
    })
}

 

阅读剩余
THE END