本文首发公众号 猩猩程序员 欢迎关注
什么是 Send Trait
Send 是 Rust 标准库中的一个重要的 marker trait,它标识那些可以在线程间安全传输的类型。
pub unsafe auto trait Send { }
核心特性
1. 自动实现 (Auto Trait)
Send 是一个 auto trait,这意味着编译器会自动为合适的类型实现它。如果一个类型的所有字段都实现了 Send,那么这个类型也会自动实现 Send。
2. Unsafe Trait
Send 被标记为 unsafe,这意味着手动实现 Send 需要使用 unsafe 代码,因为实现者需要保证类型确实可以安全地在线程间传输。
典型例子
Send 类型示例
use std::thread;
use std::sync::Arc;
fn main() {
// i32 实现了 Send,可以在线程间传输
let data = 42;
let handle = thread::spawn(move || {
println!("数据: {}", data);
});
handle.join().unwrap();
// Arc<T> 实现了 Send(当 T: Send + Sync 时)
let shared_data = Arc::new(vec![1, 2, 3, 4, 5]);
let shared_data_clone = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
println!("共享数据: {:?}", shared_data_clone);
});
handle.join().unwrap();
}
非 Send 类型示例
use std::rc::Rc;
use std::thread;
fn main() {
let rc_data = Rc::new(42);
// 以下代码会编译失败,因为 Rc<T> 没有实现 Send
// let handle = thread::spawn(move || {
// println!("RC 数据: {}", rc_data);
// });
// 错误信息: `Rc<i32>` cannot be sent between threads safely
}
为什么 Rc 不是 Send?
正如官方文档所述,Rc<T> 不实现 Send 的原因是它使用非原子操作来管理引用计数。如果两个线程同时尝试克隆指向同一个引用计数值的 Rc,它们可能会同时尝试更新引用计数,这会导致未定义行为。
use std::rc::Rc;
// Rc 内部的引用计数操作不是原子的
struct RcExample {
data: Rc<i32>,
}
// RcExample 不会自动实现 Send,因为 Rc<i32> 不是 Send
Arc vs Rc 的对比
use std::sync::Arc;
use std::rc::Rc;
use std::thread;
fn demonstrate_arc_vs_rc() {
// Arc 使用原子操作,是 Send + Sync
let arc_data = Arc::new(42);
let arc_clone = Arc::clone(&arc_data);
let handle = thread::spawn(move || {
println!("Arc 数据: {}", arc_clone); // 这是安全的
});
handle.join().unwrap();
// Rc 使用非原子操作,不是 Send
let rc_data = Rc::new(42);
// let rc_clone = Rc::clone(&rc_data);
// thread::spawn(move || {
// println!("Rc 数据: {}", rc_clone); // 编译错误!
// });
}
自定义类型的 Send 实现
自动实现 Send
// 所有字段都是 Send,所以这个结构体自动实现 Send
struct MyStruct {
data: i32,
text: String,
numbers: Vec<u32>,
}
// 可以在线程间传输
fn use_my_struct() {
let my_data = MyStruct {
data: 42,
text: "Hello".to_string(),
numbers: vec![1, 2, 3],
};
std::thread::spawn(move || {
println!("数据: {}, 文本: {}", my_data.data, my_data.text);
});
}
包含非 Send 字段
use std::rc::Rc;
// 包含 Rc,所以不会自动实现 Send
struct NotSendStruct {
data: i32,
rc_data: Rc<String>,
}
// 以下代码会编译失败
// fn try_send_not_send() {
// let not_send = NotSendStruct {
// data: 42,
// rc_data: Rc::new("Hello".to_string()),
// };
//
// std::thread::spawn(move || {
// println!("数据: {}", not_send.data); // 编译错误!
// });
// }
手动实现 Send(需要 unsafe)
use std::marker::PhantomData;
use std::ptr::NonNull;
// 一个包含原始指针的结构体
struct MyRawPointer<T> {
ptr: NonNull<T>,
_marker: PhantomData<T>,
}
// 手动实现 Send(需要确保这是安全的)
unsafe impl<T> Send for MyRawPointer<T> where T: Send {}
// 注意:这需要非常小心,确保指针的使用是线程安全的
实际应用场景
1. 线程池中的任务
use std::thread;
use std::sync::mpsc;
fn worker_example() {
let (sender, receiver) = mpsc::channel::<Box<dyn Fn() + Send>>();
// 工作线程
let handle = thread::spawn(move || {
while let Ok(task) = receiver.recv() {
task();
}
});
// 发送任务(必须是 Send)
let data = 42;
sender.send(Box::new(move || {
println!("处理数据: {}", data);
})).unwrap();
drop(sender);
handle.join().unwrap();
}
2. 异步编程
use std::future::Future;
use std::pin::Pin;
// Future 必须是 Send 才能在不同线程间调度
fn async_example() -> Pin<Box<dyn Future<Output = i32> + Send>> {
Box::pin(async {
// 异步计算
42
})
}
粽结
Send trait 是 Rust 并发安全的核心组件之一:
- 自动实现:编译器会为合适的类型自动实现 Send
- 线程安全保证:只有 Send 类型才能在线程间传输
- 与 Sync 配合:Send 和 Sync 一起构成了 Rust 的并发安全模型
- 编译时检查:违反 Send 约束会在编译时被捕获,避免运行时错误
Send trait 让 Rust 能够在编译时就保证线程间数据传输的安全性,这是 Rust 并发编程安全性的重要基础。
本文首发公众号 猩猩程序员 欢迎关注