0Pricing
Learn Rust Coding · 课时

加入线程并收集结果

等待线程完成并汇总输出。

加入线程并收集结果 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

为什么要 join 线程?

spawn 会启动工作,但您通常需要它的结果,或者至少需要确认它已经完成。join 就是等待线程的方式。

本课中,您将收集句柄、join 它们,并将它们产生的值汇总为最终答案。

join 返回 Result

join 返回一个 Result。成功时,其中包含闭包的返回值;线程发生 panic 时,其中包含 Err。

调用 unwrap 可以直接取得该值,但如果线程失败,也会继续触发 panic。

use std::thread;

fn main() {
    let h = thread::spawn(|| 7 * 6);
    let value: i32 = h.join().unwrap();
    println!("answer = {}", value);
}

先收集句柄

要并行运行线程,请先生成所有线程,再 join 其中任何一个。生成线程时,将每个句柄依次放入一个向量。

如果在生成线程的循环中立即 join,那么每个线程都会在下一个线程启动前完成,从而失去并发性。

use std::thread;

fn main() {
    let handles: Vec<_> = (0..4)
        .map(|i| thread::spawn(move || i * i))
        .collect();
    for h in handles {
        println!("{}", h.join().unwrap());
    }
}

将结果收集到 Vec 中

您可以 join 每个句柄,并将返回值放入结果向量中。

这里的生成顺序和 join 顺序都从零到三,因此结果向量最终会按可预测的顺序排列。

use std::thread;

fn main() {
    let handles: Vec<_> = (1..=4)
        .map(|n| thread::spawn(move || n * 10))
        .collect();
    let results: Vec<i32> = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .collect();
    println!("{:?}", results);
}

汇总并行结果

收集结果后,您可以对它们进行归约。这里每个线程都会计算一个部分值,然后由 main 将它们相加。

这种先 map 再 reduce 的结构是常见的并行模式。

use std::thread;

fn main() {
    let handles: Vec<_> = (1..=5)
        .map(|n| thread::spawn(move || n * n))
        .collect();
    let total: i32 = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .sum();
    println!("total = {}", total);
}

将工作拆分为多个块

对于较大的数据,可以将其拆分为多个块,并将每个块交给自己的线程。每个线程都会返回一个部分和。

将每个块克隆到一个拥有所有权的 Vec 中,可以让线程安全地拥有自己的切片。

use std::thread;

fn main() {
    let data: Vec<i32> = (1..=10).collect();
    let handles: Vec<_> = data
        .chunks(5)
        .map(|c| { let c = c.to_vec(); thread::spawn(move || c.iter().sum::<i32>()) })
        .collect();
    let total: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
    println!("{}", total);
}

处理线程 panic

如果工作线程可能发生 panic,请不要盲目调用 unwrap。应匹配 join 返回的 Result,以处理错误路径。

这样,即使某个工作线程失败,main 线程仍能继续运行。

use std::thread;

fn main() {
    let h = thread::spawn(|| {
        if true { panic!("failed"); }
        1
    });
    match h.join() {
        Ok(v) => println!("got {}", v),
        Err(_) => println!("thread panicked"),
    }
}

将结果收集到共享 Vec 中

线程不必返回值,也可以将值放入共享的 Arc<Mutex<Vec>> 中。此时顺序是不确定的。

join 所有线程后,锁定一次该向量,以读取收集到的每个结果。

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let out = Arc::new(Mutex::new(vec![]));
    let mut handles = vec![];
    for i in 0..3 {
        let o = Arc::clone(&out);
        handles.push(thread::spawn(move || o.lock().unwrap().push(i)));
    }
    for h in handles { h.join().unwrap(); }
    println!("{:?}", out.lock().unwrap());
}

作用域线程

thread::scope 允许线程借用局部数据,而无需使用 move 或 Arc。该作用域保证所有线程在它返回前完成。

这样,对栈上数据进行并行读取既方便又安全。

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    thread::scope(|s| {
        s.spawn(|| println!("sum {}", data.iter().sum::<i32>()));
        s.spawn(|| println!("len {}", data.len()));
    });
}

从作用域返回结果

作用域线程也可以通过句柄返回值。您可以在作用域内 join 它们,以收集结果。

由于作用域会阻塞直到执行完成,被借用的数据在整个过程中都保持有效。

use std::thread;

fn main() {
    let nums = vec![4, 5, 6];
    let total = thread::scope(|s| {
        let h = s.spawn(|| nums.iter().sum::<i32>());
        h.join().unwrap()
    });
    println!("total = {}", total);
}

不要忘记 join

如果丢弃 JoinHandle 而不进行 join,该线程就会变成脱离线程。它会继续运行,但您将失去它的结果,也无法再等待它。

进程也可能在线程完成前退出。需要结果或确认完成时,请进行 join。

快速检查

检验您对 join 和收集结果的理解。

回顾

您已经了解到,join 返回一个 Result,其中包含线程的值或 panic 信息。请先生成所有线程,再进行 join,以保持并发性。

您可以将结果收集到 Vec 中并进行归约,也可以将结果放入共享的 Arc<Mutex<Vec>> 中。作用域线程可以安全地借用局部数据,而丢弃句柄会使线程脱离。

现在,您已经掌握了 Rust 线程和共享状态的核心工具。

常见问题解答

「加入线程并收集结果」课时是免费的吗?

是的 — 「加入线程并收集结果」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「加入线程并收集结果」这节课中我会学到什么?

等待线程完成并汇总输出。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「加入线程并收集结果」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 创建线程
  2. 将数据移入线程
  3. 使用 Arc 和 Mutex 共享数据
  4. 加入线程并收集结果
← 返回 Learn Rust Coding