このページの目次

スレッドと同期プリミティブ

Rust 標準ライブラリの並行性ツールボックス——Mutex/RwLock は共有可変アクセスを提供し、mpsc チャネルはスレッド間でメッセージを渡す(メモリを共有しない)、Condvar は条件待機を行い、アトミック操作と Ordering はロックフリー並行処理の基盤である。各ツールは「スレッド間での情報伝達」の一種のモードに対応している。

std::thread

let handle = std::thread::spawn(|| {
    println!("hello from another thread");
});
handle.join().unwrap();              // スレッドの終了を待つ

各スレッドには独立したスタックがある(Linux ではデフォルトで 2MB、Builder::stack_size で調整可能)。std::thread::spawn のクロージャは Send + 'static でなければならない——スレッドはスプナー(生成元)よりも長く生存する可能性があるからである。

Mutex: ミューテックス

use std::sync::Mutex;
let m = Mutex::new(0);
* m.lock().unwrap() += 1;            // lock() は MutexGuard を返す
// MutexGuard がドロップされる → 自動的にアンロックされる(パニックが発生しても、unwinding 中にドロップされる)

MutexGuard<'_, T>DerefMut&mut T を提供する)と Drop(ロックを解放する)を実装している。手動で unlock() を呼び出す必要はない。ロック保持中にスレッドがパニックすると、Mutex は「ポイズン化」される——後続の lock()Err(PoisonError) を返し、データが一貫していない可能性があることを知らせてくれる。

RwLock: 読み書きロック

use std::sync::RwLock;
let lock = RwLock::new(42);
let r1 = lock.read().unwrap();       // 共有読み取りロック
let r2 = lock.read().unwrap();       // 複数のリーダーが共存可能
// let w = lock.write().unwrap();    // ブロックする: リーダーがアクティブ
drop(r1); drop(r2);                  // リーダーが去った
let mut w = lock.write().unwrap();   // ここで書き込み可能

RwLock は読み取りが多く書き込みが少ない場合に適している。ただし注意すべき点として、リーダーが絶えず存在すると、ライターが飢餓状態になる可能性がある。std::sync::RwLock は公平性(FIFO 順序など)を保証しない——具体的な動作は OS のスケジューラに依存する。

mpsc: 複数プロデューサ・単一コンシューマ チャネル

use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || { tx.send("hello").unwrap(); });
let msg = rx.recv().unwrap();        // メッセージがあるまでブロック待機

recv() はメッセージが届くまで現在のスレッドをブロックする——これは非同期ではない。非同期コンテキストでは tokio::sync::mpsc を使用すること。すべての tx がドロップされると、rx.recv()Err(RecvError) を返す——これは重要である:チャネルの「クローズ」は、送信側の Drop によって暗黙的に伝達される。

Condvar: 条件変数

use std::sync::{Arc, Mutex, Condvar};
let pair = Arc::new((Mutex::new(false), Condvar::new()));

// 待機側スレッド:
let (lock, cvar) = &*pair;
let mut guard = lock.lock().unwrap();
while !*guard {
    guard = cvar.wait(guard).unwrap();  // 原子操作: ロック解放 + ブロック
}

// 通知側スレッド:
let (lock, cvar) = &*pair;
*lock.lock().unwrap() = true;
cvar.notify_one();

条件をチェックする際には if ではなく while を使用する必要がある——偽のウェイクアップ(spurious wakeup)が発生する可能性があるためである(OS は条件が満たされていない場合でもスレッドを唤醒することがある)。

Atomic と Ordering

use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst);

Relaxed: 原子性のみを保証し、周囲のメモリ操作との順序は保証しない——用途: 単純なカウントで、他の変数との同期が不要な場合 Acquire/Release: 単方向の同期——Acquire は、Release より前に書かれた書き込みを後続の読み取りが確認できることを保証する。用途: ロックの実装 SeqCst: 最も強力——グローバルな順序一貫性を保証する。用途: デフォルトの選択。なぜより弱い順序付けを選ぶ必要があるのかを理解している場合のみ、他の順序付けを使用すること

参考

  • Rust Book: 第 16 章
  • Rustonomicon: アトミック操作、メモリ順序付け
  • CppReference: memory_order(C++ の定義は Rust のアトミックモデルにも適用される)

Keywords: thread, Mutex, RwLock, mpsc, channel, Condvar, AtomicUsize, Ordering, SeqCst, Acquire, Release