Basics of Rust Concurrency「2」

1,300 阅读2分钟

与其像上面的例子那样把一个函数传给 thread::spawn,把一个闭包当做参数传入更常见。这可以让我们能够捕获要移入新线程的值:

let numbers = vec![1, 2, 3];

thread::spawn(move || {
    for n in numbers {
        println!("{n}");
    }
});

在这里,变量 numbers 的所有权被移到新线程。这里我们必须使用一个 move closure 来转移所有权。否则即使新线程可能比变量生命周期要长,闭包也会通过引用来捕获 numbers,从而导致编译器错误。

由于一个线程可能会运行到程序执行的最后一刻,所以 spawn 对其参数类型有一个 ‘static约束。换句话说,它只接受那些(可能)永远存在的函数。一个通过引用捕获局部变量的闭包不可能永远存在,因为当局部变量不再存在时,该引用就会失效。

我们可以通过闭包中返回值从而获取到线程中的值,这个返回值可以从 join() 返回的thread::Result 中获得。

let numbers = Vec::from_iter(0..=1000);

let t = thread::spawn(move || {
    let len = numbers.len();
    let sum = numbers.into_iter().sum::<usize>();
    sum / len // 1
});

let average = t.join().unwrap(); // 2

println!("average: {average}");
  1. 闭包函数返回的值
  2. 返回的值通过 join() 回送到主线程

而如果 numbers 集合是空的,那么闭包函数中试图 sum / len 就会引发 panic。而 join() 也会返回这个 panic,导致主线程也会因为 unwrap()panic

📢 THREAD BUILDER

thread::spawn 其实等同于 thread::Builder::new().spawn().unwrap()thread::Builder 可以对新线程进行一些配置:

  1. 配置新线程的堆栈大小
  2. 给新线程一个别名 → thread::current().name() 获取线程名

而获取的线程名,会被用与 panic 信息,开发者可以在监控平台以及一些调试工具中看到

Scoped Threads

⚠️ WARNING

截至2022年3月,Scoped Thread 还不是Rust标准库的一部分。不过它已被提议并作为不稳定的特性来实现,在Rust的nightly版本中可以使用:#![feature(scoped_threads)]

如果我们确定一个子线程肯定不会超过某个作用域,那么这个线程就可以安全地借用那些不会永远存在的东西,比如局部变量(只要它们的生命周期不小于当前作用域)。

Rust标准库提供了 thread::scope 来生成这样的 Scoped Thread。我们还是可以生成新线程,而新线程不能超出我们传递给该函数的闭包的范围,从而使安全借用局部变量成为可能。

来个例子说明一下:

let numbers = vec![1, 2, 3];

thread::scope(|s| { // 1
    s.spawn(|| {    // 2
        println!("length: {}", numbers.len());
    });
    s.spawn(|| {    // 2
        for n in &numbers {
            println!("{n}");
        }
    });
});   // 3

TODO!!!

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情