在 Rust 中,线程安全通过两个自动 trait 来确保:Send 和 Sync。
Send 允许在线程之间传递类型(通过转移所有权)。Sync 保证类型可以安全地在多个线程中同时使用(通过引用)。大多数标准类型在 Rust 中默认实现这些 traits。例如,Arc<T>,Mutex<T> 都是 Send 和 Sync(如果 T 也符合这些 traits)。
您可以显式禁止或为您的类型实现这些 traits。例如,如果您有一个内部不安全字段(例如,裸指针或外部资源),应将该类型标记为 !Send 或 !Sync:
use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> 未实现 Send/Sync,因此 MyType 也不会实现这些 traits。
如果您实现低级包装并手动管理线程安全,可以手动实现 Send/Sync(不安全):
unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
确保线程安全的责任在于程序员。
如果通过 Arc<Rc<T>> 将 Rc<T> 提到多个线程,会发生什么?
人们常常认为 Arc 可以保护一切。但 Rc<T> 不实现 Send/Sync,即使被包装在 Arc 中!如下所示:
use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // 编译器不会允许这样做! }
Arc 不会弥补嵌套成员缺少 Send/Sync 的问题。
故事
在一个项目中,尝试使用 Arc<Rc<T>> 来在线程之间共享数据并非阻塞地共享所有权。程序在运行时因不可预测的行为而崩溃;结果发现 Rc 不是线程安全的,而这对 Send/Sync traits 的了解最初不足。
故事
在自制事件循环中,State 类型保存了对数据的 原始 指针。State 类型标记为 unsafe impl Send,但忘记了设置同步。最终出现了经典的数据竞争,直到发布后才被发现。
故事
开发者实现了一个 Mutex 的 newtype 包装,但错误地将其标记为 !Sync,未手动实现 Sync。这导致无法在多线程上下文中使用该类型(例如,在 Arc<Mutex<T>> 内部),因为编译器要求 Sync。通过实现 unsafe impl Sync 并进行安全性分析来修复。