10.4 Send 与 Sync trait:线程安全的类型约束

Rust 的并发安全性并非依赖运行时检查或程序员约定,而是通过类型系统在编译期强制保证。这一机制的核心是两个特殊的标记 trait:SendSync。它们不包含任何方法,仅作为编译器判断类型是否可用于并发上下文的依据。

Send:可安全跨线程转移

一个类型 T 实现了 Send,意味着其值可以安全地从一个线程移动到另一个线程。几乎所有 Rust 标准库类型都实现了 Send,例如 i32StringVec<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,当且仅当 &TSend。换句话说,多个线程可以同时持有 &T 而不会引发数据竞争

典型 Sync 类型:

  • 所有不可变的、无内部可变性的类型(如 i32String);
  • Arc<T>(只要 T: Sync + Send);
  • Mutex<T>(只要 T: Send)。

Sync 示例:

  • RefCell<T>:内部可变性依赖非原子的运行时借用检查;
  • Cell<T>:虽为 Copy,但其内部可变性在多线程下不安全。

自动实现规则

SendSync自动派生的(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(若内部类型满足条件)。

手动实现(谨慎使用)

极少数情况下,你可能需要为自定义类型手动实现 SendSync,例如封装一个线程安全的 C 库对象:

struct ThreadSafeHandle {
    ptr: *mut c_void,
}

unsafe impl Send for ThreadSafeHandle {}
unsafe impl Sync for ThreadSafeHandle {}

⚠️ 注意:这必须基于对底层实现的充分信任,否则会破坏内存安全。

小结

SendSync 是 Rust 并发模型的基石。它们以零成本的方式,在编译期阻止了绝大多数数据竞争问题。理解这两个 trait 的含义和自动实现规则,有助于你设计出既安全又高效的并发 API,并正确使用标准库提供的同步原语。在日常开发中,应优先依赖编译器的自动检查,避免不必要的 unsafe 实现。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考