歴史的に、マルチスレッドの作業はクラッシュ、レース、リークを伴うことが多く、特にメモリの制御されていない交換で問題が発生しています。Rustは、型レベルでのスレッドの安全性の概念を実装しています。オブジェクトは、必要なトレイト(Send、Sync)を実装している場合のみスレッドに渡すことができます。スレッド自体はstd::thread::spawnを介して作成され、それらの間の通信はチャネルまたは制御されたミューテーションを使用する共有メモリ(Mutex、Arc)を介して行われます。
問題: 手動での同期管理は難しく危険です。明示的な所有権の移動なしにスレッド間で任意のオブジェクトを渡すことは、レースやクラッシュを引き起こします。
解決策: 明示的に移動可能(move)なオブジェクトまたはArc、Mutexを介して共有されるオブジェクト、または組み込みのメッセージチャネル(std::sync::mpsc、crossbeam)のみを使用します。これにより、同期的および非同期のデータ交換に関連するエラーが最小限に抑えられます:所有権は常に一意です。
コード例:
use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(String::from("Hello from thread!")).unwrap(); }); let received = rx.recv().unwrap(); println!("Received: {}", received); }
主要な特長:
moveを使ってオブジェクトを渡した後、メインスレッドでそのオブジェクトを使用し続けることができますか?
いいえ、オブジェクトが移動された瞬間(例えば、thread::spawnのクロージャで)、親スレッドでそのオブジェクトを使用することはできません。コンパイラはコードをビルドできません。
ミュータブル参照(&mut T)をスレッド間で渡すことはできますか?
いいえ、ミュータブル参照&mut Tは一つのインスタンスでのみ存在でき、Sendトレイトはデフォルトでは実装されていません。変更可能なデータを扱うためには、Mutex/Arcのラッパーを使用します。
Rc<T>をスレッド間の所有権の分割に使用できないのはなぜですか?
Rc<T>はSyncおよびSendを実装していないため、内部のカウンタはスレッドセーフではありません。スレッドセーフを使用するにはArc<T>(アトミック参照カウンタ)を使用します。
// RcとArcの比較 use std::sync::Arc; let x = Arc::new(5); // 複製してスレッド間で共有できます
開発者は、Rc<String>を使用してスレッド間で文字列を共有することに決め、thread::spawn内にRcを配置しました。コードはunsafeで強制的にキャストしない限りコンパイルされず、その後アプリケーションがクラッシュしたり、破損したデータで動作したりする可能性があります。
利点:
欠点:
Arc<String> + Mutex<String>を使用して保護されたアクセスを実現するか、共有所有権なしでチャネルを介してメッセージを渡します。
利点:
欠点: