加入线程并收集结果
等待线程完成并汇总输出。
加入线程并收集结果 是 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 反馈 — 无需本地设置。
此课程中的所有课时
- 创建线程
- 将数据移入线程
- 使用 Arc 和 Mutex 共享数据
- 加入线程并收集结果