マルチスレッド環境における安全な作業の問題は、プログラマーが以前から直面しており、レースコンディション、一貫性のないデータ、メモリリークの問題に悩まされてきました。Rustでは、コンパイル時にこれらの問題を最小限に抑えるために、marker-traitであるSendとSyncが独自のアプローチで実装されています。
問題点:スレッド間で共有データへのアクセス制御が欠如していることにより、トラブルシューティングが困難なエラーが生じることです。他の多くの言語では、責任は完全にプログラマーにありますが、Rustではコンパイラがスレッド間で何を転送/共有できるかを自動的に検証します。
解決策:トレイトSendは、オブジェクトを1つのスレッドから別のスレッドに安全に転送できることを保証します。Syncは、異なるスレッドからオブジェクトへの参照を安全に共有できることを意味します。Rustのほとんどの標準型は、これらのトレイトを自動的に実装していますが、カスタム型は手動で実装するか、特定のケースでimpl !Sendまたはimpl !Syncを通じて禁止することができます。
コードの例:
use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } // counterは常に10になります、レースなし!
主な特徴:
生ポインタを含む型はSendまたはSyncですか?
いいえ、生ポインタまたはスレッドセーフ性が保証されていないリソースを含む型は、これらのトレイトを実装しません。開発者がこれらを手動で実装する場合、完全な責任を持つことになります(通常はunsafe impl Send/Sync)。
Rc<T>およびRefCell<T>はSendまたはSyncですか?
いいえ、Rc<T>とRefCell<T>はマルチスレッドでの使用に対して安全ではありません(つまりSendでもSyncでもありません)。マルチスレッドシナリオではArc<T>とMutex/RwLockが使用されます。
static変数が実装されていないSync型を含む場合、何が起こりますか?
Rustはそのようなstatic変数が存在することを許可しません:それはSyncである必要があり、そうでなければコンパイラはエラーを報告します。
若い開発者がthread::spawnにRcオブジェクトを置く—このコードはRcがスレッド間で渡されない場合にのみコンパイルされます。thread::spawnからRcを引き出そうとするとコンパイルエラーになります。なぜならRcはSendを実装せず、レースから保護されていないからです。
利点:
欠点:
Arc+Mutexを使用してマルチスレッドカウンターを作成し、すべてのスレッドがスレッドセーフなインターフェースを通じて同じデータで作業します。レースはなく、コードは安全で堅牢です。
利点:
欠点: