Rust Send 与 Sync
Send/Sync 标记 trait 如何描述跨线程移动与共享;数据竞争防护在类型系统中的落点。
#type / concept
#status / growing
#tech / dev
#resource / rust
[!info] 关联笔记
Rust Send 与 Sync
这个概念为什么出现
“无数据竞争”不是靠文档约定,而是靠类型:
- 哪些值可以移到别的线程?→
Send - 哪些值可以通过共享引用被多线程碰?→
Sync
多数类型自动实现;实现错误会在编译期拒绝危险代码。
[!abstract] 一句话理解
T: Send表示所有权可安全转移到其他线程;T: Sync表示&T可安全共享到其他线程(等价于&T: Send)。
最小可运行示例
场景:把只读配置 Arc 分发给工作线程
配置在主线程构建,工作线程只读共享。Arc<String>: Send + Sync(在 String: Sync 等条件下)允许这样做。
use std::sync::Arc;
use std::thread;
// 业务意图:多线程只读共享配置。
// 教学点:Arc 使共享所有权;Send/Sync 让编译通过。
fn main() {
let cfg = Arc::new(String::from("mode=prod"));
let mut handles = vec![];
for i in 0..3 {
let cfg = Arc::clone(&cfg);
handles.push(thread::spawn(move || {
println!("worker{i} cfg={cfg}");
}));
}
for h in handles { h.join().unwrap(); }
}
建议运行:cargo run
期望:三个 worker 都打印同一配置(顺序不定)。
结合场景再看三个关注点
Rc不能这样跨线程(非线程安全计数)- 共享可变需要 Mutex 等 才能让内部可变也安全
- marker trait 无方法,却约束整个并发生态
核心概念与准确模型
| Trait | 含义(直觉) |
|---|---|
Send | 可转移所有权到其他线程 |
Sync | 可共享引用到其他线程 |
关系直觉:T 是 Sync ⇔ &T 是 Send。
常见:
- 绝大多数基础类型:
Send + Sync Rc:非Send非SyncRefCell:Send但非Sync(简化记忆需查具体;关键是不能多线程共享借用检查)MutexGuard等通常不Send到其他线程(实现细节)
设计动机
- 编译期消灭数据竞争类 bug
- 不安全实现必须用
unsafe impl并证明不变量 - API 作者用 trait bound 表达线程契约
边界与误区
- 自动 trait 规则有例外与条件实现
unsafe impl Send/Sync是高风险- 满足 Send/Sync ≠ 无死锁
[!warning] 常见误区:Sync 就是“类型可变也随便共享” 共享可变仍需同步原语。
工程实践
- 跨线程用
Arc/channel而不是裸共享 - 库文档写清线程安全
- 不要随手 unsafe impl
- 用编译器错误学习边界
本节总结
- Send/Sync 是并发类型根基
- 决定 Rc vs Arc、RefCell vs Mutex
- 安全并发的编译期合同
自测题
- 为什么
Rc<T>一般不能传入thread::spawn? Arc<Mutex<T>>解决了什么?
参考答案
- 非原子引用计数,不是 Send/Sync 安全组合。
- 多线程共享所有权 + 互斥访问内部 T。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Send and Sync | 官方书 | Send/Sync |
| std::marker::Send | 标准库 | Send |
| Nomicon — Send and Sync | 官方 | 深层规则 |