Rustでは、スレッド操作の安全性は2つの自動トレイト、SendとSyncによって確保されます。
Send は、型をスレッド間で(所有権を渡すことによって)転送することを可能にします。Sync は、型が複数のスレッドから同時に安全に使用できることを保証します(参照を介して)。Rustの標準的な多くの型は、デフォルトでこれらのトレイトを実装しています。例えば、Arc<T>, Mutex<T>は、Tもこれらのトレイトに準拠している場合にSendとSyncを実装します。
自分の型に対して、これらのトレイトを明示的に禁止または実装することができます。例えば、内部に非安全なフィールド(生ポインタや外部リソースなど)がある場合、その型を!Sendまたは!Syncとして作成する必要があります:
use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32>はSend/Syncを実装していないため、MyTypeもこれらのトレイトを実装しません。
低レベルのラッパーを実装し、スレッド安全性を手動で管理する場合、Send/Syncを手動で実装することができます(unsafe):
unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
これはプログラマの責任です—スレッド安全性を保証すること。
Rc<T>をArc<Rc<T>>経由で複数のスレッドに渡したらどうなりますか?
Arcがすべてを保護していると思われることがよくあります。しかし、Rc<T>はSend/Syncを実装していません、Arcでラップしても!その例は次の通りです:
use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // コンパイラはこれを許可しません! }
Arcは内部メンバーのSend/Syncの欠如を補うものではありません。
ストーリー
プロジェクトでは、スレッド間でデータを共有し、非ブロッキング的に所有権を分け合うためにArc<Rc<T>>を使用しようとしました。プログラムは実行中に予測できない動作でクラッシュしました;実際、Rcはスレッド安全ではなく、Send/Syncトレイトについての知識が最初に不足していました。
ストーリー
自作のイベントループで、State型はデータへの生ポインタを保持していました。State型はunsafe impl Sendをマークしましたが、同期を設置するのを忘れました。その結果、リリース後に発見された古典的なデータレースが発生しました。
ストーリー
開発者はMutexのnewtypeラッパーを実装しましたが、誤って!Syncにしてしまい、Syncを手動で実装しませんでした。このため、スレッドのコンテキスト(例えばArc<Mutex<T>>の内部)でその型を使用することができませんでした;コンパイラはSyncを要求しました。unsafe impl Syncを実装し、安全性の分析によって修正されました。