📌 スレッド
最も基本的な並列処理はスレッドを作成することです.スレッドを作成するには thread::spawn
関数を使います.引数にはクロージャを指定します.
use std::thread;
thread::spawn(|| {
// thread code
});
thread::spawn
関数はJoinHandle
型を返します. join
メソッドを呼び出すことで終了を待ちます. join
メソッドはResult型を返します.
let handle = thread::spawn(|| {
// thread code
});
handle.join().unwrap();
クロージャの環境をスレッド間で共有することは通常の方法ではできません.コピーを作成できるなら,環境にコピーされますが,そうでないなら所有権を移動しなければなりません.その場合は move
を使います.
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
複数のスレッド間で状態を共有するには 排他制御 が必要です.これを行うためにMutex
型があります. lock
メソッドでリソースをロックします.lockメソッドはLockResult
型を返します.また, LockResult型は RAII である MutexGuard
型のオブジェクトを束縛しているので,自動でロックを解除します.
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
排他制御を行うMutex型はできましたが,このオブジェクトをスレッド間で共有しなければなりません.所有権の共有はRc型でできますが,これのマルチスレッド版であるArc
型を使う必要があります.
use std::sync::Arc;
fn main() {
let counter = Arc::new(Mutex::new(0));
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
...
おや,またこの図が出てきました.
マルチスレッド版のRc型であるArc型,排他制御を行うMutex型の関係を表すと次のようになります.
📌 Send + Sync
マルチスレッドではオブジェクトの所有権の操作を考慮する必要があります.オブジェクトの所有権がスレッド間で移動できる場合, Send
トレイト(Sendマーカートレイト
)のインスタンスになります. マーカートレイト とは,メソッドを持たないトレイトのことで,トレイト境界に使うためのものです.Sizedトレイトもマーカートレイトの1つです.次に,複数のスレッドから安全に参照できる場合はSync
トレイトのインスタンスになります.これは参照&T
がSend
トレイトのインスタンスならば, T
型はSync
トレイトのインスタンスであり,参照が別のスレッドに送ることができるという意味になります.
ほとんどのプリミティブ型はSendトレイトとSyncトレイトのインスタンスです.また,Syncトレイトのインスタンスであるデータ型で構成された型は,それもまたSyncトレイトのインスタンスです.これは自動的にそれぞれのインスタンスになります.マルチスレッドに対応していないRc型はSendトレイトのインスタンスでもなく,Syncトレイトのインスタンスでもありません(かわりに !Send
や !Sync
の非実装マーカートレイトがつきます).それに対して,Arc型はSendトレイトとSyncトレイトのインスタンスです.また,Arc型が束縛する型もまたSendトレイトとSyncトレイトのインスタンスである必要があります.もし,そうでないならMutex型を利用します.
次の図はRc型とArc型のドキュメントからの抜粋です.Trait Implementations
にありますが,新規に定義したデータ型などは基本的にSendマーカートレイトとSyncマーカートレイトは自動的につくので Auto Trait Implementations
で確認できます.
std::rc::Rc
std::sync::Arc
📌 RwLock
排他制御を行うのはMutex型以外にRwLock
型があります.Mutex型は常に1つのスレッドがリソースの操作できますが,RwLock型の場合は,不変参照だけなら複数のスレッドが同時にリソースをロックすることができ,可変参照のときだけ,1つのスレッドに制限するものです.他にアトミック変数というのもあります.これはプリミティブ型のみしか扱えませんが,Mutex型よりは高速に動作します.処理速度に問題がなければ基本的にMutex型を使うのがいいでしょう.RwLock型やアトミック変数の詳細は公式リファレンスなどを参照してください.