Rust Send 与 Sync

Send/Sync 标记 trait 如何描述跨线程移动与共享;数据竞争防护在类型系统中的落点。

#type / concept #status / growing #tech / dev #resource / rust

[!info] 关联笔记

Rust Send 与 Sync

这个概念为什么出现

“无数据竞争”不是靠文档约定,而是靠类型:

  • 哪些值可以移到别的线程?→ Send
  • 哪些值可以通过共享引用被多线程碰?→ Sync

多数类型自动实现;实现错误会在编译期拒绝危险代码。

[!abstract] 一句话理解 T: Send 表示所有权可安全转移到其他线程;T: Sync 表示 &T 可安全共享到其他线程(等价于 &T: Send)。

最小可运行示例

场景:把只读配置 Arc 分发给工作线程

配置在主线程构建,工作线程只读共享。Arc<String>: Send + Sync(在 String: Sync 等条件下)允许这样做。

use std::sync::Arc;
use std::thread;

// 业务意图:多线程只读共享配置。
// 教学点:Arc 使共享所有权;Send/Sync 让编译通过。

fn main() {
    let cfg = Arc::new(String::from("mode=prod"));
    let mut handles = vec![];
    for i in 0..3 {
        let cfg = Arc::clone(&cfg);
        handles.push(thread::spawn(move || {
            println!("worker{i} cfg={cfg}");
        }));
    }
    for h in handles { h.join().unwrap(); }
}

建议运行:cargo run

期望:三个 worker 都打印同一配置(顺序不定)。

结合场景再看三个关注点

  1. Rc 不能这样跨线程(非线程安全计数)
  2. 共享可变需要 Mutex 等 才能让内部可变也安全
  3. marker trait 无方法,却约束整个并发生态

核心概念与准确模型

Trait含义(直觉)
Send可转移所有权到其他线程
Sync可共享引用到其他线程

关系直觉:TSync&TSend

常见:

  • 绝大多数基础类型:Send + Sync
  • Rc:非 SendSync
  • RefCellSend 但非 Sync(简化记忆需查具体;关键是不能多线程共享借用检查)
  • MutexGuard 等通常不 Send 到其他线程(实现细节)

设计动机

  • 编译期消灭数据竞争类 bug
  • 不安全实现必须用 unsafe impl 并证明不变量
  • API 作者用 trait bound 表达线程契约

边界与误区

  • 自动 trait 规则有例外与条件实现
  • unsafe impl Send/Sync 是高风险
  • 满足 Send/Sync ≠ 无死锁

[!warning] 常见误区:Sync 就是“类型可变也随便共享” 共享可变仍需同步原语。

工程实践

  1. 跨线程用 Arc/channel 而不是裸共享
  2. 库文档写清线程安全
  3. 不要随手 unsafe impl
  4. 用编译器错误学习边界

本节总结

  • Send/Sync 是并发类型根基
  • 决定 Rc vs Arc、RefCell vs Mutex
  • 安全并发的编译期合同

自测题

  1. 为什么 Rc<T> 一般不能传入 thread::spawn
  2. Arc<Mutex<T>> 解决了什么?
参考答案
  1. 非原子引用计数,不是 Send/Sync 安全组合。
  2. 多线程共享所有权 + 互斥访问内部 T。

延伸阅读与资料来源

资料类型支撑内容
The Book — Send and Sync官方书Send/Sync
std::marker::Send标准库Send
Nomicon — Send and Sync官方深层规则
创建于 2026/7/15 更新于 2026/7/15