Rust 线程
std::thread 启动线程、move 闭包转移所有权、JoinHandle 等待与线程间基本协作入口。
#concept
#growing
#dev
#rust
[!info] related notes
Rust 线程
这个概念为什么出现
CPU 密集或需要并行的工作会用到 OS 线程。
Rust 标准库提供 std::thread,并把“什么能跨线程移动”交给类型系统(Send)。
[!abstract] 一句话理解
thread::spawn启动线程执行闭包(常用 move 捕获);通过JoinHandle等待结果;跨线程移动的数据必须满足Send。
最小可运行示例
场景:报表服务并行汇总两个分区计数
夜间任务把两个分区的行数并行统计,主线程 join 后相加。
use std::thread;
// 业务意图:并行统计两个分区的记录数。
// 教学点:无捕获闭包无需 move;先 spawn 两个线程,再分别 join。
fn count_partition(name: &str, rows: u64) -> u64 {
println!("counting {name}");
rows // 示意
}
fn main() {
let h1 = thread::spawn(|| count_partition("p1", 120));
let h2 = thread::spawn(|| count_partition("p2", 80));
let r1 = h1.join();
let r2 = h2.join(); // 先等待两者,再检查各自结果
let total = r1.expect("p1 panicked") + r2.expect("p2 panicked");
println!("total_rows={total}");
}
建议运行:cargo run
期望输出(顺序可能交错):
counting p1
counting p2
total_rows=200
结合场景再看三个关注点
- join 处理线程 panic(
Result) - spawn 闭包与返回值均要求 Send + static;移动借用仍受原数据寿命限制,借用局部数据可用 thread::scope
- 线程不是 async 任务,更重
核心概念与准确模型
spawn→JoinHandle<T>join等待并取Result<T>- 线程局部:
thread_local! - Builder 可命名/设栈大小
与 channel/Mutex 组合才构成完整并发。
设计动机
- 安全地暴露 OS 线程
- 通过 Send 防数据竞争
- 显式 join 表达生命周期
边界与误区
- 无界创建线程可耗尽资源 → 线程池
- 丢弃 JoinHandle 会分离线程,不会自动等待或取消;main 返回后进程可结束其他线程
- join 的 Err 是线程 panic;若线程函数返回 Result,需区分 join 的外层错误与业务内层错误
- thread::scope 在作用域退出前等待作用域线程,因此可借用栈数据;普通 spawn 不具有这一保证
- 在 async 运行时里阻塞线程要谨慎
[!warning] 常见误区:在 async 函数里 thread::sleep 当并发 会阻塞工作线程;async 用定时器 future。
工程实践
- CPU 密集可考虑线程池
- I/O 密集优先 async 生态
- 命名线程便于排障
- 传播错误:join 后处理 panic 或返回值
本节总结
- spawn/join 基础
- move + Send
- 与 channel/锁组合
自测题
- 为什么 spawn 的闭包常常要
move? join返回Err通常表示?
参考答案
- 将非 Copy 的拥有数据转交线程;仍需满足 Send + static,move 一个短借用本身不够。
- 子线程 panic。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Fearless Concurrency | 官方书 | 并发 |
| std::thread | 标准库 | 线程 API |