ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

comprehensive-rust 并发入门:深入解析 Rust 线程(Plain Threads 与 Scoped Threads)

2026/9/10 21:53:32 拓冰建站 浏览量
comprehensive-rust 并发入门:深入解析 Rust 线程(Plain Threads 与 Scoped Threads) comprehensive-rust 并发入门深入解析 Rust 线程Plain Threads 与 Scoped Threads【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本文以 Google Android 团队维护的 Rust 培训课程 comprehensive-rust 中Threads章节为核心系统讲解 Rust 中基于操作系统线程的并发编程如何通过std::thread::spawn创建线程、如何用JoinHandle等待线程结束并取回返回值、如何处理线程 panic以及如何用thread::scope让线程安全地借用外部数据。读完本文你将掌握 Rust 线程从创建、同步到安全借用数据的完整实战路径并理解无惧并发fearless concurrency在类型系统层面的保障原理。一、课程脉络Threads 章节在 comprehensive-rust 中的位置在 comprehensive-rust 的 并发Concurrency课程 中线程是第一天上午的第一个主题由三部分构成见 src/SUMMARY.mdThreads章节总览页通过{{%segment outline}}占位符自动渲染本节大纲Plain Threads基础线程建议授课 15 分钟Scoped Threads作用域线程建议授课 13 分钟。课程定位明确指出Rust 使用操作系统线程搭配互斥锁mutex与通道channel实现完整的并发支持而类型系统把大量并发 bug 变成编译期错误——这正是无惧并发的核心思想。线程章节正是这一理念的第一站即使不引入任何特殊并发语法借用检查器也能保证线程间数据访问的内存安全。补充说明总览页 src/concurrency/threads.md 本身只有标题与{{%segment outline}}宏。这个宏并非普通 Markdown而是由仓库中的构建工具 mdbook-course 在渲染时替换。在 mdbook-course/src/replacements.rs 中[segment, outline]会被解析为调用当前 Segment 的outline()方法其底层实现在 mdbook-course/src/course.rs按章节顺序生成Plain Threads / Scoped Threads的课程大纲列表。这也解释了为什么本小节的知识主体分布在两个子页面中。二、Plain Threads用thread::spawn创建第一个线程Rust 线程的用法与其他语言非常相似。以下来自 src/concurrency/threads/plain.md 的完整示例展示了主线程与子线程并行计数use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { for i in 0..10 { println!(Count in thread: {i}!); thread::sleep(Duration::from_millis(5)); } }); for i in 0..5 { println!(Main thread: {i}); thread::sleep(Duration::from_millis(5)); } }代码要点如下thread::spawn(closure)创建线程std::thread::spawn接收一个闭包闭包即新线程要执行的代码。这里通过thread::sleep各睡 5ms让主线程与子线程基本保持同步推进5ms 的粒度足够宽松二者输出会交替出现。子线程不阻塞main结束运行上面的例子会发现程序往往在子线程数到 10 之前就退出了。原因在于main函数返回即进程结束而thread::spawn创建的线程不会被自动等待。这一点与 C 的std::thread/boost::thread/ POSIXpthreads的 join 语义形成对比——那些语言的线程对象析构时通常需要显式 join 或 detach而 Rust 默认不等待。线程 panic 相互独立某个线程发生 panic 不会导致其他线程或主线程崩溃。panic 还可以携带 payload载荷事后可通过Any::downcast_ref拆解出具体类型详见下文。2.1 用JoinHandle::join()等待线程完成既然子线程不阻塞进程退出如何等待它跑完thread::spawn的返回值正是答案它返回一个JoinHandle其上提供阻塞式的.join()方法。use std::thread; fn main() { let handle thread::spawn(|| { for i in 0..10 { println!(Count in thread: {i}!); } }); handle.join().unwrap(); // 至此子线程必然已经结束主线程才会继续 }把thread::spawn(...)的返回值保存为let handle ...再在需要同步的位置调用handle.join()主线程就会阻塞等待子线程执行完毕程序从而可以完整计数到 10。2.2 从线程取回返回值thread::ResultTthread::spawn的闭包返回类型是T而JoinHandle::join()的返回类型是thread::ResultT——即ResultT, Boxdyn Any Send static。换句话说闭包里的return值可以通过join()的Ok分支取回若线程 panicjoin()返回Err其中携带 panic 的 payload。use std::thread; fn main() { let handle thread::spawn(|| { let mut sum 0; for i in 0..10 { sum i; } sum // 闭包的返回值 T i32 }); match handle.join() { Ok(value) println!(线程返回值{value}), Err(payload) println!(线程 panic载荷{payload:?}), } }Err(payload)中的payload是Boxdyn Any Send这正是课程中顺带讲解Any的时机可以用payload.downcast_ref::String()等操作把 panic 载荷还原为具体类型。2.3 线程如何取输入闭包捕获与move返回值解决了线程向外传数据的问题那向内传数据呢闭包天然支持捕获外部变量use std::thread; fn main() { let data String::from(Hello); let handle thread::spawn(move || { // 使用 data 计算派生值 data.len() }); let _ handle.join(); }这里有一个关键教训直接按引用捕获通常会触发编译错误。因为thread::spawn要求闭包满足static生命周期普通借用无法保证跨线程存活编译器会提示must move it。解决办法是显式move把变量所有权移入线程闭包在闭包内完成计算并返回派生值。这也自然引出了下一个问题——如果想借用而非转移所有权该怎么办三、Scoped Threads用thread::scope安全借用外部数据普通线程无法借用环境中的数据。下面这段代码来自 src/concurrency/threads/scoped.md编译必然失败use std::thread; fn foo() { let s String::from(Hello); thread::spawn(|| { dbg!(s.len()); }); } fn main() { foo(); }失败的根本原因非常深刻如果foo()返回而子线程还在运行子线程访问的s将指向已失效的栈帧造成栈上 use-after-return直接违反内存安全。普通thread::spawn创建的线程没有生命周期约束Rust 编译器宁可拒绝编译也不允许悬垂引用。3.1thread::scope带生命周期的有界线程解决方案是标准库提供的作用域线程scoped threadthread::scopeuse std::thread; fn foo() { let s String::from(Hello); thread::scope(|scope| { scope.spawn(|| { dbg!(s.len()); }); }); } fn main() { foo(); }同样的代码把thread::spawn换成thread::scope(|scope| scope.spawn(...))后即可编译通过。其安全保证来自一个简单的机制当thread::scope的函数体执行完毕返回时所有在该作用域内创建的线程都已被保证 join 完成。因此这些线程可以安全地返回借用自外部栈的数据——借用期严格被限制在 scope 调用之内。由于所有子线程的生命周期都封在thread::scope内部借用检查器可以确认子线程结束先于外部栈帧销毁从而放行非static的引用捕获。而作用域内部依然适用普通的 Rust 借用规则要么被一个线程可变借用mutable borrow要么被任意多个线程不可变借用shared borrow。这保证了即使在多线程场景下也绝不可能出现数据竞争。3.2 实战对比什么时候用哪种 API场景推荐 API原因线程需要借用调用方栈上的数据thread::scope生命周期被约束在作用域内编译期保证安全线程完全自包含、数据全部move进去thread::spawn无需等待所有线程结束灵活度更高需要等待线程完成并取回结果spawnJoinHandle::join()join 返回thread::ResultT可同时处理 panic需要让多个子线程并发读写不同数据thread::scope借用规则让编译器替你检查数据竞争四、延伸阅读从线程到完整并发工具箱线程只是并发拼图的第一块。在 comprehensive-rust 课程中紧接着 Channels通道 与 Shared State共享状态 主题分别覆盖mpsc通道以及ArcMutex的组合——三者共同构成 Rust 标准库层的并发三件套消息传递std::sync::mpsc通道有界/无界线程间通过发送消息协作共享内存ArcMutexT在多个线程间共享可变状态同步原语配合Send/Sync标记 trait 在编译期约束类型可否跨线程传递与共享。所有练习可在 并发同步练习如哲学家就餐问题、链接检查器中巩固验证对异步编程感兴趣的读者可进一步学习课程中的 async/await 与 tokio 运行时。五、小结通过 comprehensive-rust 的 Threads 章节可以提炼出三条核心结论创建与同步thread::spawn(closure)创建线程返回JoinHandlehandle.join()阻塞等待并返回thread::ResultT从而既能取回结果也能捕获 panic 载荷。数据输入闭包捕获外部变量需要move转移所有权若想借用则必须改用thread::scope其返回前保证 join 所有线程的语义让借用检查器能够安全放行非static引用。安全哲学Rust 不依赖运行时检查来防数据竞争而是把线程生命周期、借用与Send/Sync约束全部交给编译期——这就是无惧并发的具体体现也是本课程希望传授给学习者的核心思维方式。相关源码与文档线程章节正文见 src/concurrency/threads/plain.md 与 src/concurrency/threads/scoped.md章节大纲的自动生成逻辑见 mdbook-course/src/replacements.rs 与 mdbook-course/src/course.rs课程定位与日程见 src/concurrency/welcome.md。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考