编程系统库开发者 / 后端开发者

Rust 中的“线程安全”(Send 和 Sync)是如何工作的,以及如何为用户自定义类型实现/限制它们?

用 Hintsage AI 助手通过面试

答案。

在 Rust 中,线程安全通过两个自动 trait 来确保:SendSync

  • Send 允许在线程之间传递类型(通过转移所有权)。
  • Sync 保证类型可以安全地在多个线程中同时使用(通过引用)。

大多数标准类型在 Rust 中默认实现这些 traits。例如,Arc<T>Mutex<T> 都是 SendSync(如果 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 并进行安全性分析来修复。