到達点は、データを渡す方法と共有する方法を選び、完了を待つことです。前提は第14回です。
共有しないことで単純にする
別スレッドへ仕事を渡し、結果だけをチャネルで戻すと、共有状態への同時更新を減らせます。moveクロージャは必要な所有物をスレッドへ渡すためによく使います。
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let mut handles = Vec::new();
for value in [10, 20, 30] {
let sender = tx.clone();
handles.push(thread::spawn(move || {
sender.send(value * 2).expect("受信側は生存中");
}));
}
drop(tx);
let total: i32 = rx.iter().sum();
for handle in handles { handle.join().expect("教材の処理はpanicしない"); }
println!("{total}");
let count = Arc::new(Mutex::new(0));
let shared = Arc::clone(&count);
let worker = thread::spawn(move || {
*shared.lock().expect("ロックは正常") += 1;
});
worker.join().expect("教材の処理はpanicしない");
println!("{}", *count.lock().expect("ロックは正常"));
}
src/main.rsで120と1が表示されます。結果が届く順番は保証していませんが、合計は変わりません。元のtxをdropするのは、すべての送信側がなくなったら受信ループを終えるためです。送信側を一つ残すと、まだ届くかもしれないと待ち続けます。
型が防ぐことと防がないこと
Sendは所有値をスレッド間で移せる性質、Syncは共有参照をスレッド間で扱える性質に関わります。Rcがそのままスレッド共有に使えないのは、単なる不便ではなく前提が異なるためです。
Mutexはロック中の操作を保護します。ただしデッドロックや業務上の競合まで自動で防ぐわけではありません。二つのロックを逆順で取得しない、ロック中に長い通信をしない、必要な一連の確認と更新を同じ範囲で守るなどの設計が必要です。
例のexpectは教材の固定された前提です。実アプリでは受信側の終了、ワーカーのpanic、ロックのpoisoningをどう扱うか決めます。
練習と解答
練習:大量の仕事を無制限に送ると、何が問題になりますか。
解答:受信側より送信側が速いとキューが増え、メモリを消費します。容量を持つチャネルや一定数のワーカーを使い、送り手にも待ってもらう背圧を設計します。
公式資料
Rust全20回の目次 | 前の回 | 次の回