使用 SyncSend trait 的可擴展並發

ch16-04-extensible-concurrency-sync-and-send.md
commit 426f3e4ec17e539ae9905ba559411169d303a031

Rust 的並發模型中一個有趣的方面是:語言本身對並發知之 甚少。我們之前討論的幾乎所有內容,都屬於標準庫,而不是語言本身的內容。由於不需要語言提供並發相關的基礎設施,並發方案不受標準庫或語言所限:我們可以編寫自己的或使用別人編寫的並發功能。

然而有兩個並發概念是內嵌於語言中的:std::marker 中的 SyncSend trait。

通過 Send 允許在執行緒間轉移所有權

Send 標記 trait 表明類型的所有權可以在執行緒間傳遞。幾乎所有的 Rust 類型都是Send 的,不過有一些例外,包括 Rc<T>:這是不能 Send 的,因為如果複製了 Rc<T> 的值並嘗試將複製的所有權轉移到另一個執行緒,這兩個執行緒都可能同時更新引用計數。為此,Rc<T> 被實現為用於單執行緒場景,這時不需要為擁有執行緒安全的引用計數而付出性能代價。

因此,Rust 類型系統和 trait bound 確保永遠也不會意外的將不安全的 Rc<T> 在執行緒間發送。當嘗試在範例 16-14 中這麼做的時候,會得到錯誤 the trait Send is not implemented for Rc<Mutex<i32>>。而使用標記為 SendArc<T> 時,就沒有問題了。

任何完全由 Send 的類型組成的類型也會自動被標記為 Send。幾乎所有基本類型都是 Send 的,除了第十九章將會討論的裸指針(raw pointer)。

Sync 允許多執行緒訪問

Sync 標記 trait 表明一個實現了 Sync 的類型可以安全的在多個執行緒中擁有其值的引用。換一種方式來說,對於任意類型 T,如果 &TT 的引用)是 Send 的話 T 就是 Sync 的,這意味著其引用就可以安全的發送到另一個執行緒。類似於 Send 的情況,基本類型是 Sync 的,完全由 Sync 的類型組成的類型也是 Sync 的。

智慧指針 Rc<T> 也不是 Sync 的,出於其不是 Send 相同的原因。RefCell<T>(第十五章討論過)和 Cell<T> 系列類型不是 Sync 的。RefCell<T> 在運行時所進行的借用檢查也不是執行緒安全的。Mutex<T>Sync 的,正如 “在執行緒間共享 Mutex<T> 部分所講的它可以被用來在多執行緒中共享訪問。

手動實現 SendSync 是不安全的

通常並不需要手動實現 SendSync trait,因為由 SendSync 的類型組成的類型,自動就是 SendSync 的。因為他們是標記 trait,甚至都不需要實現任何方法。他們只是用來加強並發相關的不可變性的。

手動實現這些標記 trait 涉及到編寫不安全的 Rust 代碼,第十九章將會講述具體的方法;當前重要的是,在創建新的由不是 SendSync 的部分構成的並發類型時需要多加小心,以確保維持其安全保證。The Rustonomicon 中有更多關於這些保證以及如何維持他們的訊息。

總結

這不會是本書最後一個出現並發的章節:第二十章的項目會在更現實的場景中使用這些概念,而不像本章中討論的這些小例子。

正如之前提到的,因為 Rust 本身很少有處理並發的部分內容,有很多的並發方案都由 crate 實現。他們比標準庫要發展的更快;請在網路上搜索當前最新的用於多執行緒場景的 crate。

Rust 提供了用於消息傳遞的通道,和像 Mutex<T>Arc<T> 這樣可以安全的用於並發上下文的智慧指針。類型系統和借用檢查器會確保這些場景中的代碼,不會出現數據競爭和無效的引用。一旦代碼可以編譯了,我們就可以堅信這些程式碼可以正確的運行於多執行緒環境,而不會出現其他語言中經常出現的那些難以追蹤的 bug。並發編程不再是什麼可怕的概念:無所畏懼地並發吧!

接下來,讓我們討論一下當 Rust 程序變得更大時,有哪些符合語言習慣的問題建模方法和結構化解決方案,以及 Rust 的風格是如何與面向對象編程(Object Oriented Programming)中那些你所熟悉的概念相聯繫的。