10.4 Send 与 Sync trait:线程安全的类型约束
听
Rust 的并发安全性并非依赖运行时检查或程序员约定,而是通过类型系统在编译期强制保证。这一机制的核心是两个特殊的标记 trait:Send 和 Sync。它们不包含任何方法,仅作为编译器判断类型是否可用于并发上下文的依据。
Send:可安全跨线程转移
一个类型 T 实现了 Send,意味着其值可以安全地从一个线程移动到另一个线程。几乎所有 Rust 标准库类型都实现了 Send,例如 i32、String、Vec<T>(只要 T: Send)等。
反例包括:
Rc<T>:引用计数非原子,跨线程修改计数会导致数据竞争;- 原始指针(如
*const T):无所有权语义,无法保证指向内存的有效性。
当你尝试将非 Send 类型传入线程时,编译器会报错:
use std::rc::Rc;
use std::thread;
let rc = Rc::new(5);
thread::spawn(move || {
println!("{}", rc); // ❌ 编译错误:`Rc<i32>` is not `Send`
});
Sync:可安全跨线程共享引用
一个类型 T 实现了 Sync,当且仅当 &T 是 Send。换句话说,多个线程可以同时持有 &T 而不会引发数据竞争。
典型 Sync 类型:
- 所有不可变的、无内部可变性的类型(如
i32、String); Arc<T>(只要T: Sync + Send);Mutex<T>(只要T: Send)。
非 Sync 示例:
RefCell<T>:内部可变性依赖非原子的运行时借用检查;Cell<T>:虽为Copy,但其内部可变性在多线程下不安全。
自动实现规则
Send 和 Sync 是自动派生的(auto traits):
- 若结构体或枚举的所有字段都是
Send,则该类型也是Send; - 若所有字段都是
Sync,则该类型也是Sync。
因此,自定义类型通常无需手动实现这两个 trait。只有在封装 unsafe 代码或外部资源(如 C 库句柄)时,才需谨慎考虑是否应手动实现(使用 unsafe impl)。
与并发原语的关系
标准库中的并发工具对类型有明确约束:
thread::spawn<F>要求F: Send + 'static;Arc<T>要求T: Send + Sync才能跨线程共享;Mutex<T>要求T: Send,因为锁释放后值可能被移至另一线程。
例如,Arc<Mutex<T>> 能用于多线程,是因为:
Mutex<T>: Sync(允许多个线程持有&Mutex<T>);Mutex<T>: Send(若T: Send),允许整个互斥体在线程间转移;Arc<...>: Send + Sync(若内部类型满足条件)。
手动实现(谨慎使用)
极少数情况下,你可能需要为自定义类型手动实现 Send 或 Sync,例如封装一个线程安全的 C 库对象:
struct ThreadSafeHandle {
ptr: *mut c_void,
}
unsafe impl Send for ThreadSafeHandle {}
unsafe impl Sync for ThreadSafeHandle {}
⚠️ 注意:这必须基于对底层实现的充分信任,否则会破坏内存安全。
小结
Send 和 Sync 是 Rust 并发模型的基石。它们以零成本的方式,在编译期阻止了绝大多数数据竞争问题。理解这两个 trait 的含义和自动实现规则,有助于你设计出既安全又高效的并发 API,并正确使用标准库提供的同步原语。在日常开发中,应优先依赖编译器的自动检查,避免不必要的 unsafe 实现。
#Rust 入门教程
分享于 1 周前