Published on

Send与Sync:深入解析

1. Send与Sync的基本概念

1.1 Send trait

Send trait 表示类型的所有权可以安全地跨线程传递。当一个类型实现了Send,意味着该类型的值可以从一个线程移动到另一个线程而不会引发数据竞争。

pub unsafe auto trait Send {}
  • 自动特质(auto trait):大多数类型会自动实现Send
  • 不安全特质(unsafe trait):实现Send需要开发者保证线程安全
  • 所有权转移:只涉及所有权的移动,不涉及借用的共享

1.2 Sync trait

Sync特质表示类型的引用可以安全地在线程间共享。即&T是线程安全的。

pub unsafe auto trait Sync {}
  • 共享引用的线程安全性
  • Sync类型允许不可变引用(&T)跨线程共享
  • 可变引用(&mut T)的线程安全由其他机制保证

2. 自动实现规则

2.1 Send的自动实现

大多数类型自动实现Send,除非包含以下 非Send 类型:

  • 原始指针(*const T, *mut T)
  • Rc<T>(引用计数非原子)
  • Cell/RefCell(非线程安全的内部可变性)

2.2 Sync的自动实现

类型T实现Sync当且仅当:

  • &T实现Send
  • 或者T是基本类型、不可变类型
  • 或者T使用线程安全的内部可变性(如 Mutex<T> )

3. 常见类型的Send+Sync状态

| 类型            | Send | Sync | 说明                          |
|-----------------|------|------|-----------------------------|
| i32, f64等基本类型 ||| 基本类型都是线程安全的           |
| String          ||| 拥有所有权的字符串              |
| Vec<T>          ||*   |T是Send时 `Vec<T>` 是Send+Sync   |
| Box<T>          |*   |*   | 跟随T的Send/Sync状态           |
| Rc<T>           ||| 非原子引用计数                 |
| Arc<T>          |*   |*   | 原子引用计数,跟随T的Sync状态   |
| Mutex<T>        |*   || 要求T实现Send                 |
| RwLock<T>       |*   || 要求T实现Send+Sync            |
| Cell<T>         |*   || 要求T实现Copy,非Sync          |
| RefCell<T>      ||| 非线程安全的内部可变性          |
| *const T/*mut T | ✗    | ✗    | 原始指针不自动实现Send/Sync     |
  • ✓*表示自动实现,但可能需要额外的条件
  • 表示不自动实现
  • 表示自动实现

4. 手动实现Send和Sync

由于这两个trait是unsafe的,手动实现需要特别小心:

struct MyType(*mut u8);

// 安全实现:保证指针访问是线程安全的
unsafe impl Send for MyType {}
unsafe impl Sync for MyType {}

实现前必须确保:

  1. 没有数据竞争可能性
  2. 内存访问是同步的
  3. 没有未定义行为

5. 实际应用场景

5.1 跨线程传递数据

use std::thread;

let data = vec![1, 2, 3];  // Vec<i32>实现了Send

thread::spawn(move || {
    println!("{:?}", data);  // 所有权转移到新线程
}).join().unwrap();

5.2 共享不可变数据

use std::sync::Arc;

let shared_data = Arc::new(42);  // Arc<T>要求T: Send + Sync

let handles: Vec<_> = (0..10).map(|_| {
    let data = Arc::clone(&shared_data);
    thread::spawn(move || {
        println!("{}", *data);
    })
}).collect();

for h in handles {
    h.join().unwrap();
}

5.3 线程安全的内部可变性

use std::sync::{Arc, Mutex};

let counter = Arc::new(Mutex::new(0));

let handles: Vec<_> = (0..10).map(|_| {
    let counter = Arc::clone(&counter);
    thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    })
}).collect();

for h in handles {
    h.join().unwrap();
}

6. 高级主题

6.1 PhantomData与Send/Sync

当结构体包含标记字段时,PhantomData可以影响Send/Sync实现:

use std::marker::PhantomData;

struct NotSend(*const u8);
struct MyStruct<T> {
    data: i32,
    _marker: PhantomData<T>,
}

// 只有当T是Send时MyStruct<T>才是Send
unsafe impl<T: Send> Send for MyStruct<T> {}
unsafe impl<T: Sync> Sync for MyStruct<T> {}

6.2 自动特质排除

可以通过负实现(negative impl)阻止自动实现:

#![feature(negative_impls)]

impl !Send for MySpecialType {}
impl !Sync for MySpecialType {}

6.3 泛型约束

在编写泛型代码时,常常需要Send/Sync约束:

fn process<T: Send + 'static>(value: T) -> thread::JoinHandle<T> {
    thread::spawn(move || {
        // 处理value
        value
    })
}

7. 常见误区与陷阱

  1. 误认为Send意味着Sync:实际上这两个特质是独立的
  2. 忽略生命周期:跨线程传递的数据通常需要'static生命周期
  3. 过度同步:不必要的Sync约束会限制API的灵活性
  4. 误用内部可变性:在Sync类型中使用Cell会导致未定义行为

8. 最佳实践

  1. 优先使用标准库提供的线程安全类型(Arc, Mutex等)
  2. 对自定义的unsafe实现进行严格测试
  3. 在公开API中明确标记Send/Sync要求
  4. 使用PhantomData来控制Send/Sync实现
  5. 避免在多线程环境中使用非线程安全的类型

THE END