深入理解Rust中的 Send 和 Sync trait
Rust中的標記 trait(Marker Trait)Send 和 Sync 是并發(fā)安全的核心基石。用于標記類型的并發(fā)安全屬性,讓編譯器在編譯時檢查線程間數(shù)據(jù)傳遞的合法性,從根源上避免數(shù)據(jù)競爭(Data Race)。
Send:類型的值能不能在線程之間“移動所有權”。Sync:類型的值能不能在多個線程中“共享引用 &T”。- 對同一個值進行
并發(fā)訪問(只讀)是內(nèi)存安全的。 并發(fā)修改仍然要靠鎖/原子/其他機制。
- 對同一個值進行

Send
一個類型 T 是 Send,表示把 T 的一個 值 從一個線程移動到另一個線程,不會引發(fā)數(shù)據(jù)競爭或內(nèi)存安全問題。
- 移動的是 所有權,而不是引用。
- 移動之后,原線程不能再用這個值(所有權已經(jīng)被 move)。
絕大多數(shù)類型自動實現(xiàn) Send,以下一些例外(!Send):
Rc<T>:引用計數(shù)是非原子操作,跨線程轉移會導致計數(shù)競爭(數(shù)據(jù)競爭)。RefCell<T>/Cell<T>:內(nèi)部可變性無同步機制,跨線程轉移后,多個線程可能同時修改數(shù)據(jù)。- 裸指針
*const T/*mut T:無安全保證,直接跨線程轉移可能導致懸垂指針或數(shù)據(jù)競爭。 UnsafeCell<T>:內(nèi)部可變性的底層實現(xiàn),本身!Send(但基于它的線程安全類型如Mutex會手動實現(xiàn)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 不會導致數(shù)據(jù)競爭)。
任何允許通過 共享引用 &T 獲得可變訪問 且沒有同步保護的類型,都不是 Sync:
Cell<T>/RefCell<T>:允許通過&T改變內(nèi)部數(shù)據(jù)(“內(nèi)部可變性”),但不保證線程安全。Rc<T>:通過共享引用可以克隆Rc得到更多引用,從而在多線程下導致競爭。- 非線程安全的 FFI 對象,如果通過
&能調用會修改內(nèi)部狀態(tài)的方法。
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,編譯器會根據(jù)類型的組成部分自動推導實現(xiàn)。
| 性質 | 含義 | 常見例子 |
|---|---|---|
Send + Sync | 可以在線程間移動,也可以多線程共享引用 | i32, Mutex<T>等 |
Send + !Sync | 可以在線程間移動,但不能多線程共享引用 | 一些 *mut T封裝,Sender<T> |
!Send + Sync | 不能跨線程移動;不移動,只讀共享是安全的(較少見) | 少數(shù)特殊 FFI 對象 |
!Send + !Sync | 既不能跨線程移動,也不能多線程共享引用 | Rc<RefCell<T>> 等 |
只有在特殊情況下(寫 底層庫 / FFI 封裝 / 自己的鎖和原子數(shù)據(jù)結構 時)才會需要手動實現(xiàn)Send 和 Sync :
unsafe impl Send/unsafe impl Sync是非常嚴肅的承諾- 要自己保證所有可能的并發(fā)訪問路徑都不會產(chǎn)生數(shù)據(jù)競爭、懸垂指針等。
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)); // 裸指針指向堆內(nèi)存
SafePtr(Mutex::new(ptr))
}
// 安全訪問:通過 Mutex 加鎖,保證獨占訪問
fn get(&self) -> Option<&T> {
let guard = self.0.lock().ok()?;
unsafe { (*guard).as_ref() } // 裸指針解引用需 unsafe,但鎖保證安全
}
}
// 手動實現(xiàn) Send/Sync:因為內(nèi)部用 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!("線程內(nèi)訪問:{}", ptr_clone2.get().unwrap()); // 安全
}).join().unwrap();
}到此這篇關于深入理解Rust中的 Send 和 Sync trait的文章就介紹到這了,更多相關Rust Send 和 Sync trait內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Rust用宏實現(xiàn)參數(shù)可變的函數(shù)的實現(xiàn)示例
本文主要介紹了Rust用宏實現(xiàn)參數(shù)可變的函數(shù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-03-03

