可延展的並行與 Sync
及 Send
特徵
有一個有趣的點是 Rust 語言提供的並行功能並沒有很多。本章節討論到的並行功能幾乎都來自於標準函式庫,並不是語言本身。你能處理並行的選項並不限於語言或標準函式庫,你可以寫出你自己的並行功能或使用其他人提供的。
然而,還有有兩個並行概念深植於語言中,那就是 std::marker
中的 Sync
與 Send
特徵。
透過 Send
來允許所有權能在執行緒間轉移
Send
標記特徵(marker traits)指定有實作 Send
特徵的型別才能將其數值的所有權在執行緒間轉移。幾乎所有的 Rust 型別都有 Send
,但有些例外。這包含 Rc<T>
,此型別沒有 Send
是因為如果你克隆了 Rc<T>
數值並嘗試轉移克隆的所有權到其他執行緒,會有兩條執行緒可能同時更新參考計數。基於此原因,Rc<T>
是用於當你不想要付出執行緒安全效能開銷時而在單一執行緒使用的情況。
因此 Rust 的型別系統與特徵界限確保你無法意外不安全地傳送 Rc<T>
數值到其他執行緒。當我們嘗試範例 16-14 時,我們就會得到錯誤 the trait Send is not implemented for Rc<Mutex<i32>>
。當我們切換成有實作 Send
的 Arc<T>
的話,程式碼就能編譯通過。
任何由具有 Send
的型別所組成的型別也都會自動標記為 Send
。幾乎所有原始型別都是 Send
,除了我們將在第十九章提及的裸指標(raw pointers)。
透過 Sync
來允許多重執行緒存取
Sync
標記特徵指定有實作 Sync
的型別都能安全從多個執行緒來參考。換句話說,對於任何型別 T
,如果 &T
(對 T
的不可變參考)有 Send
的話,T
就是 Sync
的,這代表參考可以安全地傳給其他執行緒。與 Send
類似,原始型別都是 Sync
,所以由具有 Sync
的型別所組成的型別也都有 Sync
。
智慧指標 Rc<T>
沒有 Sync
的原因和沒有 Send
的原因一樣。RefCell<T>
型別(我們在第十五章提過)與其 Cell<T>
也都沒有 Sync
。 RefCell<T>
在執行時的借用檢查實作沒有執行緒安全。智慧指標 Mutex<T>
才有 Sync
並能像你在「在數個執行緒間共享 Mutex<T>
」段落看到的那樣用來在多個執行緒間分享存取。
手動實作 Send
與 Sync
是不安全的
因為由具有 Send
與 Sync
的型別組成的型別自動就會有 Send
與 Sync
,我們不需要親自實作這些特徵。至於標記特徵,它們甚至沒有任何方法需要實作。它們只是用於強制確保並行相關的不變性。
要手動實作這些特徵會需要實作不安全(unsafe)的 Rust 程式碼。我們會在第十九章討論如何使用不安全的 Rust 程式碼,現在最重要的資訊是要從不具有 Send
與 Sync
的元件來組成新的並行型別需要格外小心才能確保其安全保障。「The Rustonomicon」 有更多關於這些保障與如何維持它們的資訊。
總結
這不會是你在本書中最後一次看到並行程式碼,第二十章的專案將會在更實際的場合中使用本章節的概念,而非這裡討論的簡單範例。
如之前提過的,因為 Rust 語言本身很少處理並行的部分,許多並行解決方案都實作成 crate。這些 crate 通常發展的比標準函式庫還快,所以別忘了到線上尋找目前最先進的 crate 來在多執行緒場合中使用喔。
Rust 標準函式庫提供訊息傳遞的通道與智慧指標,像是 Mutex<T>
與 Arc<T>
,能夠在並行環境中安全使用。型別系統與借用檢查器中確保使用這些解決方案的程式碼不會發生資料競爭或是無效參考。一旦你讓你的程式碼能編譯通過後,你可以放心地認定它會開開心心地在多執行緒中執行,並且不會發生任何在其他語言中常見且難以追蹤的程式錯誤。並行程式設計就不再是個令人害怕的概念,無畏無懼地開發並行程式吧!
接下來,我們要討論當你的 Rust 程式成長時,定義出問題並組織解決辦法的慣用方案。除此之外,我們也將討論 Rust 有哪些與物件導向程式設計(object-oriented programming)類似的概念。