[Rust]Mutexを使った並行処理

2023/09/14に公開

はじめに

Rustでは並行プログラミングを安全かつ簡単に行えるように、いくつかの同期プリミティブが提供されています。その一つがMutexです。Mutexは排他制御を提供し、一度に1つのスレッドだけがデータにアクセスできるようにします。

実装

RustのMutexstd::sync::Mutexモジュールに定義されています。Mutexを使用するには、データをMutexでラップし、そのMutexを共有リソースとして使用します。

またArcは参照のカウントを行うスマートポインタです。Atomically Reference Countedの略です。

以下の例では、複数のスレッドから共有カウンタに安全にアクセスする方法を示します。

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    // counterが何回使われてるかを表示します
    println!("count first: {}", Arc::strong_count(&counter));

    for i in 0..3 {
        let counter_arc = Arc::clone(&counter);
        println!("count: {}", Arc::strong_count(&counter));
        let handle = thread::spawn(move || {
            println!("Thread {} attempting to acquire lock...", i);
            let mut num = counter_arc.lock().unwrap();
            println!("Thread {} acquired lock", i);

            *num += 1;
            println!("Thread {} incremented counter to {}", i, *num);

            // スレッドがロックを持っている間に少しスリープして、
            // 他のスレッドがロックを待っていることを示します
            thread::sleep(Duration::from_millis(50));

            println!("Thread {} releasing lock", i);
        });
        handles.push(handle);

    }

    for handle in handles {
	// join()でスレッドが終わるのを待つ
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

joinに関しては日本語ドキュメントが詳しいです。

こちらの実行結果は以下です。

count first: 1
count: 2
count: 3
count: 4
Thread 0 attempting to acquire lock...
Thread 1 attempting to acquire lock...
Thread 1 acquired lock
Thread 1 incremented counter to 1
Thread 2 attempting to acquire lock...
Thread 1 releasing lock
Thread 0 acquired lock
Thread 0 incremented counter to 2
Thread 0 releasing lock
Thread 2 acquired lock
Thread 2 incremented counter to 3
Thread 2 releasing lock
Result: 3

全てのスレッド内の処理が始まる前にprintln!("count: {}", Arc::strong_count(&counter));が実行されてるので、スレッドを建てる処理はそこそこ重いのかなぁと思います。
あとは、各スレッドに関して、acquired lockからreleasing lockの間に別のスレッドのacquired lockがないことが分かります。
つまり、Mutexを使うことで、一つの変数に対して同時に処理が行われないということが分かりました。

参考文献

Discussion