rust-concurrency - 背景
歴史的経緯
並行処理の課題
- スレッド間でメモリを共有
- ロックによる排他制御
- デッドロック、データ競合の問題 - CSP(Communicating Sequential Processes)
- アクターモデル(Erlang)
- 共有状態を避ける - マルチコアの普及
- 並行性と並列性の区別
- async/await の台頭Rustのアプローチ
// コンパイル時にデータ競合を検出
// Send: 別スレッドに転送可能
// Sync: 複数スレッドから同時アクセス可能
// Mutex<T>: T: Send なら Mutex<T>: Sync
// → 異なるスレッドから安全にアクセス可能
コンピュータサイエンス的な意味
Send と Sync
// Send: 所有権をスレッド間で転送可能
// - ほとんどの型は Send
// - Rc は Send でない(参照カウントが非アトミック)
// Sync: &T を複数スレッドに共有可能
// - T: Sync ⟺ &T: Send
// - RefCell は Sync でない(借用チェックが非スレッドセーフ)
データ競合の定義
データ競合は以下の3条件が全て成立する場合に発生:
1. 2つ以上のスレッドが同じメモリにアクセス
2. 少なくとも1つが書き込み
3. アクセスが同期されていない
Rustの所有権システムはこれを静的に防止
メモリ順序
use std::sync::atomic::Ordering;
// Relaxed: 順序保証なし(カウンタ向け)
// Acquire/Release: 同期ペア
// SeqCst: 最も強い保証(デフォルト)
counter.fetch_add(1, Ordering::Relaxed);
実践での活用
Webサーバー
// リクエストごとにスレッドプールから処理
let pool = ThreadPool::new(num_cpus::get());
for stream in listener.incoming() {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
データ処理パイプライン
// チャネルで処理をつなぐ
let (tx1, rx1) = mpsc::channel();
let (tx2, rx2) = mpsc::channel();
// ステージ1: 読み込み
thread::spawn(move || {
for line in reader.lines() {
tx1.send(line.unwrap()).unwrap();
}
});
// ステージ2: 変換
thread::spawn(move || {
for line in rx1 {
tx2.send(process(line)).unwrap();
}
});
// ステージ3: 出力
for result in rx2 {
println!("{}", result);
}
キャッシュシステム
use std::sync::RwLock;
use std::collections::HashMap;
struct Cache<K, V> {
data: RwLock<HashMap<K, V>>,
}
impl<K: Eq + Hash, V: Clone> Cache<K, V> {
fn get(&self, key: &K) -> Option<V> {
self.data.read().unwrap().get(key).cloned()
}
fn set(&self, key: K, value: V) {
self.data.write().unwrap().insert(key, value);
}
}
実世界とのギャップ
デッドロックは防げない
// Rustでもデッドロックは発生しうる
let a = Arc::new(Mutex::new(1));
let b = Arc::new(Mutex::new(2));
// スレッド1: a → b の順でロック
// スレッド2: b → a の順でロック
// → デッドロック!
// 対策: ロック順序を統一
async vs スレッド
// スレッド: I/O ブロッキングOK
// - スレッド生成コスト(数MB)
// - コンテキストスイッチコスト
// async: I/O で yield
// - タスク生成コスト小
// - ランタイム必要(tokio等)
性能のトレードオフ
| 方式 |
競合少 |
競合多 |
| Mutex |
高速 |
待ち発生 |
| RwLock |
読み取り高速 |
書き込み待ち |
| Atomic |
最速 |
複雑な操作不可 |
| Channel |
オーバーヘッド |
スケーラブル |