問題の歴史:
マルチスレッド処理は、ほとんどのプログラミング言語においてエラーの原因となり得る:データ競合、リソースの競争、明らかでないバグ。このため、Rustの創設者たちはC++とJavaの経験を考慮し、スレッド安全性のメカニズムを型システムに組み込むことにしました。これにより、多くのエラーがコンパイル時に発見されることになります。
問題:
従来の言語では、プログラマーの規律と外部ツールに頼ることがよくあります。データの所有権の転送、共有される可変メモリ、同時アクセスに関する制御の欠如は、重大なシステムエラーを引き起こす可能性があります。コンパイル時にメモリ競合が発生しないことを保証するシステムが必要でした。
解決策:
Rustでは、スレッド間でのデータの同期と転送には、標準ライブラリからの特別な型が使用されます—例えば、Arc、Mutex、およびチャネルです。重要な役割を果たすのは、トレイトSendおよびSyncのマーカーで、これらはコンパイラによって自動的に確認されます。型はスレッド安全と見なされるには、次の条件を満たす必要があります:
Sync)Sendを実装している場合に限り、スレッド間で転送可能であるコードの例:
use std::sync::{Arc, Mutex}; use std::thread; fn main() { 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(); } println!("Result: {}", *counter.lock().unwrap()); }
主な特徴:
Mutex、RwLock、チャネルは、契約上スレッド安全ですArc、Mutex)を介して行われますRc<T>をスレッド間でデータを転送するために使用できないのはなぜですか?
Rc<T>はトレイトSendを実装しておらず、スレッド安全ではありません—内部実装はノンブロッキングの参照カウンタに基づいているため、複数のスレッドからアクセスするとデータ競合が発生します。スレッドにはArc<T>を使用してください。
独自の型のために手動でSendまたはSyncを実装して、コンパイラの制限を回避できますか?
できますが、これは非常に危険です!不変量を破ると(例えば、裸のポインタを共有するなど)、データ競合が発生します。手動の実装は、型のスレッド安全性に完全に自信がある専門家にだけ任せてください。
RustでMutexがデッドロックを引き起こす可能性があるのはいつですか?それを避けるにはどうすればよいですか?
デッドロックは、複数のミューテックスの取得順序が不安定な場合、または同じスレッドで再帰的にロックがかけられた場合に発生します(Mutexは再入可能ではありません)。
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic: deadlock!
開発者は、ウェブサーバー間で状態を転送するためにRc<RefCell<T>>を使用しました。ローカルテストでは機能しましたが、プロダクション環境ではレース条件が発生しました:変数が「状態を失う」ことがあり、サーバーがクラッシュしました。
メリット:
デメリット:
状態の転送にArc<Mutex<T>>を使用し、Send/Syncを厳格に遵守し、チャネルを介してタスクをスレッドに分散し、共通データに対するmutを使用しないこと。
メリット:
デメリット: