Rust 的 Send
和 Sync
trait,这是 Rust 并发编程的核心机制,让 Rust 能在编译期防止数据竞争。
1. 为什么需要 Send 和 Sync?
想象你有一个玩具:
- 问题1:你能把玩具给另一个城市的朋友吗?(跨线程传递)
- 问题2:多个朋友能同时玩这个玩具吗?(跨线程共享)
在 Rust 中:
Send
解决第一个问题:能否安全转移所有权到其他线程Sync
解决第二个问题:能否安全地在线程间共享引用
2. Send:安全传递的通行证
2.1 什么是 Send?
- 表示类型可以安全地跨线程传递所有权
- 就像玩具带有"可邮寄"标签,可以安全寄给朋友
2.2 哪些类型实现了 Send?
rust
// 基本类型都是 Send
let a: i32 = 42; // Send ✓
let b: String = "hello".to_string(); // Send ✓
// 标准库中的线程安全类型
let c: Mutex<i32> = Mutex::new(0); // Send ✓
let d: Arc<i32> = Arc::new(42); // Send ✓
2.3 哪些类型不是 Send?
rust
// Rc 不是线程安全的
use std::rc::Rc;
let e: Rc<i32> = Rc::new(42); // 不是 Send ✗
// 原始指针不是自动 Send
let f: *const i32 = &42 as *const i32; // 不是 Send ✗
2.4 实际意义
rust
use std::thread;
// 正确示例:传递 String(实现了 Send)
thread::spawn(move || {
let s = String::from("hello");
println!("{}", s);
});
// 错误示例:尝试传递 Rc
let rc = Rc::new(42);
thread::spawn(move || {
println!("{}", rc); // 编译错误:`Rc<i32>` cannot be sent between threads safely
});
3. Sync:安全共享的许可证
3.1 什么是 Sync?
- 表示类型的不可变引用可以安全地在多个线程中使用
- 就像玩具带有"可共享"标签,多个朋友可以同时看(但不能同时修改)
3.2 哪些类型实现了 Sync?
rust
// 基本不可变类型
let a: &i32 = &42; // Sync ✓
// 线程安全的包装类型
let b: Arc<i32> = Arc::new(42); // Sync ✓
let c: Mutex<i32> = Mutex::new(0); // Sync ✓
// 原子类型
use std::sync::atomic::AtomicI32;
let d: AtomicI32 = AtomicI32::new(0); // Sync ✓
3.3 哪些类型不是 Sync?
rust
// Cell 和 RefCell 不是线程安全的
use std::cell::RefCell;
let e: RefCell<i32> = RefCell::new(42); // 不是 Sync ✗
// Rc 也不是 Sync
use std::rc::Rc;
let f: Rc<i32> = Rc::new(42); // 不是 Sync ✗
3.4 实际意义
rust
use std::thread;
use std::sync::Arc;
// 正确示例:共享 Arc(实现了 Sync)
let data = Arc::new(42);
for _ in 0..5 {
let data = Arc::clone(&data);
thread::spawn(move || {
println!("{}", data); // 多个线程同时读取
});
}
// 错误示例:尝试共享 RefCell
let bad = RefCell::new(42);
thread::spawn(move || {
println!("{}", bad.borrow()); // 编译错误:`RefCell<i32>` cannot be shared between threads
});
4. Send 和 Sync 的关系
4.1 核心关系表
类型 T 的特性 | 含义 | 常见例子 |
---|---|---|
T: Send |
可以安全移动到其他线程 | i32 , String , Mutex<T> |
T: Sync |
可以安全在线程间共享引用 | i32 , &str , Arc<T> |
T: Send + Sync |
既可移动又可共享 | Arc<Mutex<T>> , AtomicI32 |
!Send |
不能安全移动到其他线程 | Rc<T> , 原始指针 |
!Sync |
不能安全共享引用 | Cell<T> , RefCell<T> |
4.2 重要规则
-
自动派生规则:
- 如果类型的所有字段都是
Send
,那么该类型也是Send
- 如果类型的所有字段都是
Sync
,那么该类型也是Sync
- 如果类型的所有字段都是
-
引用规则:
&T
是Send
当且仅当T: Sync
&mut T
是Send
当且仅当T: Send
-
智能指针规则:
Box<T>
:Send
当且仅当T: Send
Arc<T>
:Send
和Sync
当且仅当T: Send + Sync
5. 深入原理:编译器如何保证安全
5.1 编译期检查
Rust 编译器在编译时检查:
rust
fn spawn_thread<T: Send>(data: T) {
thread::spawn(move || {
use_data(data);
});
}
- 这里
T: Send
约束确保只有可安全传递的类型才能使用
5.2 错误示例分析
rust
use std::rc::Rc;
let rc = Rc::new(42);
thread::spawn(move || {
println!("{}", rc);
});
编译器会报错:
rust
error[E0277]: `Rc<i32>` cannot be sent between threads safely
--> src/main.rs:10:5
|
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `Rc<i32>` cannot be sent between threads safely
|
= help: the trait `Send` is not implemented for `Rc<i32>`
5.3 为什么 Rc 不是 Send/Sync?
Rc
使用非原子引用计数- 如果多个线程同时修改计数,会导致计数错误
- 而
Arc
使用原子操作,所以是线程安全的
6. 实际应用模式
模式1:构建自定义线程安全类型
rust
use std::sync::{Arc, Mutex};
struct SafeCounter {
count: Mutex<i32>,
}
impl SafeCounter {
fn new() -> Self {
SafeCounter { count: Mutex::new(0) }
}
fn increment(&self) {
let mut lock = self.count.lock().unwrap();
*lock += 1;
}
}
// 自动实现 Send + Sync
unsafe impl Send for SafeCounter {} // 安全:因为内部类型都是线程安全的
unsafe impl Sync for SafeCounter {} // 安全:因为内部锁保证安全
模式2:线程隔离数据
rust
// 故意使用 !Send 类型来限制数据在单线程
struct GuiContext {
widgets: Vec<Rc<Widget>>, // Rc 不是 Send
}
impl GuiContext {
// 确保只在创建线程使用
fn update(&mut self) {
// GUI更新操作
}
}
// 明确标记为 !Send
impl !Send for GuiContext {}
模式3:跨线程通信
rust
use std::sync::mpsc;
use std::thread;
// 通道两端自动实现 Send
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
sender.send("hello from thread").unwrap();
});
println!("{}", receiver.recv().unwrap());
7. 常见问题解答
Q: 为什么 Mutex 既是 Send 又是 Sync? A:
Send
:因为可以安全移动到其他线程Sync
:因为多个线程可以共享&Mutex
(通过锁机制保证安全访问)
Q: 为什么 RefCell 不是 Sync? A: 因为它的借用检查在运行时进行,不是线程安全的。多个线程同时调用 borrow_mut()
可能导致数据竞争。
Q: 如何让自定义类型实现 Send/Sync? A: 大多数情况下编译器会自动实现。如果需要手动实现:
rust
unsafe impl Send for MyType {} // 确保没有非Send字段
unsafe impl Sync for MyType {} // 确保没有非Sync字段
但需要非常小心,确保真正满足线程安全要求。
Q: 遇到 "trait bound not satisfied" 错误怎么办? A: 通常是因为:
- 尝试在线程间传递非 Send 类型 → 改用 Arc/Mutex
- 尝试在线程间共享非 Sync 类型 → 使用原子类型或锁