rust中的标记 trait(marker trait)send 和 sync 是并发安全的核心基石。用于标记类型的并发安全属性,让编译器在编译时检查线程间数据传递的合法性,从根源上避免数据竞争(data race)。
send:类型的值能不能在线程之间“移动所有权”。sync:类型的值能不能在多个线程中“共享引用 &t”。- 对同一个值进行
并发访问(只读)是内存安全的。 并发修改仍然要靠锁/原子/其他机制。
- 对同一个值进行

send
一个类型 t 是 send,表示把 t 的一个 值 从一个线程移动到另一个线程,不会引发数据竞争或内存安全问题。
- 移动的是 所有权,而不是引用。
- 移动之后,原线程不能再用这个值(所有权已经被 move)。
绝大多数类型自动实现 send,以下一些例外(!send):
rc<t>:引用计数是非原子操作,跨线程转移会导致计数竞争(数据竞争)。refcell<t>/cell<t>:内部可变性无同步机制,跨线程转移后,多个线程可能同时修改数据。- 裸指针
*const t/*mut t:无安全保证,直接跨线程转移可能导致悬垂指针或数据竞争。 unsafecell<t>:内部可变性的底层实现,本身!send(但基于它的线程安全类型如mutex会手动实现send)。
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// vec<i32> 是 send,通过move把所有权移到线程中
let handle = thread::spawn(move || {
println!("{:?}", v);
});
handle.join().unwrap();
}sync
一个类型 t 是 sync,表示可以安全地被多个线程共享(即 &t 是 send)。即,如果一个类型的引用 &t 能安全跨线程传递,那么这个类型就是 sync(多个线程持有 &t 不会导致数据竞争)。
任何允许通过 共享引用 &t 获得可变访问 且没有同步保护的类型,都不是 sync:
cell<t>/refcell<t>:允许通过&t改变内部数据(“内部可变性”),但不保证线程安全。rc<t>:通过共享引用可以克隆rc得到更多引用,从而在多线程下导致竞争。- 非线程安全的 ffi 对象,如果通过
&能调用会修改内部状态的方法。
use std::sync::{arc, mutex};
use std::thread;
fn main() {
let data = arc::new(mutex::new(5)); // arc<mutex<i32>>
let data_clone = data.clone();
thread::spawn(move || {
let mut guard = data_clone.lock().unwrap(); // 加锁,独占访问
*guard += 1;
}).join().unwrap();
println!("{}", data.lock().unwrap()); // 输出 6
}auto trait
send 和 sync 都是 auto trait,编译器会根据类型的组成部分自动推导实现。
| 性质 | 含义 | 常见例子 |
|---|---|---|
send + sync | 可以在线程间移动,也可以多线程共享引用 | i32, mutex<t>等 |
send + !sync | 可以在线程间移动,但不能多线程共享引用 | 一些 *mut t封装,sender<t> |
!send + sync | 不能跨线程移动;不移动,只读共享是安全的(较少见) | 少数特殊 ffi 对象 |
!send + !sync | 既不能跨线程移动,也不能多线程共享引用 | rc<refcell<t>> 等 |
只有在特殊情况下(写 底层库 / ffi 封装 / 自己的锁和原子数据结构 时)才会需要手动实现send 和 sync :
unsafe impl send/unsafe impl sync是非常严肃的承诺- 要自己保证所有可能的并发访问路径都不会产生数据竞争、悬垂指针等。
use std::sync::mutex;
// 自定义类型:用 mutex 保护裸指针(裸指针本身 !send/!sync)
struct safeptr<t>(mutex<*mut t>);
impl<t> safeptr<t> {
fn new(value: t) -> self {
let ptr = box::into_raw(box::new(value)); // 裸指针指向堆内存
safeptr(mutex::new(ptr))
}
// 安全访问:通过 mutex 加锁,保证独占访问
fn get(&self) -> option<&t> {
let guard = self.0.lock().ok()?;
unsafe { (*guard).as_ref() } // 裸指针解引用需 unsafe,但锁保证安全
}
}
// 手动实现 send/sync:因为内部用 mutex 保护,访问裸指针是线程安全的
unsafe impl<t> send for safeptr<t> {}
unsafe impl<t> sync for safeptr<t> {}
// 测试:跨线程共享 safeptr
fn main() {
let ptr = safeptr::new(5);
let ptr_clone = arc::new(ptr);
let ptr_clone2 = ptr_clone.clone();
thread::spawn(move || {
println!("线程内访问:{}", ptr_clone2.get().unwrap()); // 安全
}).join().unwrap();
}到此这篇关于深入理解rust中的 send 和 sync trait的文章就介绍到这了,更多相关rust send 和 sync trait内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论