Chapter 18

並列処理

mebiusbox
mebiusbox
2023.03.16に更新
このチャプターの目次

📌 スレッド

最も基本的な並列処理はスレッドを作成することです.スレッドを作成するには 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;
        });
        ...

おや,またこの図が出てきました.

rust-ownership01

マルチスレッド版のRc型であるArc型,排他制御を行うMutex型の関係を表すと次のようになります.

rust-arc

📌 Send + Sync

マルチスレッドではオブジェクトの所有権の操作を考慮する必要があります.オブジェクトの所有権がスレッド間で移動できる場合, Sendトレイト(Sendマーカートレイト)のインスタンスになります. マーカートレイト とは,メソッドを持たないトレイトのことで,トレイト境界に使うためのものです.Sizedトレイトもマーカートレイトの1つです.次に,複数のスレッドから安全に参照できる場合はSyncトレイトのインスタンスになります.これは参照&TSendトレイトのインスタンスならば, 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 で確認できます.

rust-send+sync-rc

std::rc::Rc

rust-send+sync-arc

std::sync::Arc

📌 RwLock

排他制御を行うのはMutex型以外にRwLock型があります.Mutex型は常に1つのスレッドがリソースの操作できますが,RwLock型の場合は,不変参照だけなら複数のスレッドが同時にリソースをロックすることができ,可変参照のときだけ,1つのスレッドに制限するものです.他にアトミック変数というのもあります.これはプリミティブ型のみしか扱えませんが,Mutex型よりは高速に動作します.処理速度に問題がなければ基本的にMutex型を使うのがいいでしょう.RwLock型やアトミック変数の詳細は公式リファレンスなどを参照してください.