項目17:状態共有並列実行には気を付けよう
https://effective-rust.com/deadlock.html
並列処理と Rust
状態を共有する 並列処理 は以下の 3 つの問題を引き起こす
データ競合: データが壊れる可能性がある
データ競合 と Rust
Send トレイトと Sync トレイト
デッドロック: プログラムが突然止まってしまう可能性がある
デットロック と Rust
競合状態: 実行のタイミング次第で不整合な状態になる
radish-miyazaki.icon が追加
デットロック と Rust#679c843975d04f0000f03e16
これらの問題の多くは 非決定的 に発生し、負荷がかかった時に起こりやすいため、デバッグ が困難
まとめ
状態共有並列に起因する問題を避けるには、単に状態共有並列を避けることである
具体的には、
Do not communicate by sharing memory; instead, share memory by communicating.
(メモリを共有して通信してはいけない; 通信してメモリを共有しよう。)
Effective Go からの引用
Go ではこの目的に適した チャネル を言語自体に組み込んでいる
Rust でも同等の機能が std::sync:mspc モジュールで提供されている
channel 関数を((Sender, Receiver) ペアを返す)を用いると特定の型の値をスレッド間で受け渡すことができる
もし状態共有並列が避けられない場合は、デッドロック を避けるためにいかに気をつけよう
相互に整合する必要があるデータ構造は 1 つの mutex で管理しよう
デットロック と Rust#679c8b8975d04f0000f03e2d
ロックのスコープは小さく自明にしよう
可能ならば、ロックの取得・解放を 1 箇所にまとめたヘルパメソッドも用意しよう
デットロック と Rust#679c825575d04f0000f03df0
ロックを保持したままクロージャを呼び出すのはやめよう ??
∵ クロージャ内でロックを保持すると、後から追加されるコード次第でデッドロックが発生するリスクがあるため
e.g.
code:rs
struct SharedState {
data1: Mutex<i32>,
data2: Mutex<i32>,
}
impl SharedState {
fn with_lock<F>(&self, f: F)
where
F: FnOnce(&mut i32),
{
let mut data = self.data1.lock().unwrap();
f(&self.data2); // ロックを保持したままクロージャを実行
}
}
with_lock のクロージャ内で他のロックを取るコードが追加されると、デッドロックが発生しうる
code:rs
let cloned_state = Arc::clone(&state);
let handle = thread::spawn(move || {
state.with_lock(|val| {
*val += 1;
let _lock2 = mutex.lock().unwrap();
});
});
let cloned_state2 = Arc::clone(&state);
let handle2 = thread::spawn(move || {
let _lock2 = cloned_state2.data2.lock().unwrap();
let _lock1 = cloned_state2.data1.lock().unwrap();
});
MutexGuard を呼び出し元に返さないようにしよう
データ競合 と Rust#679c63a675d04f0000fe5411
∵ MutexGuard はドロップされた時点でロックを解除するが、呼び出し元に戻すことでロックのスコープが不明確になり、デッドロックや長時間のロック保持のリスクが増すため
CI システムにデッドロック検出ツールを加えよう
項目32:CIシステムを設定しよう
e.g. no_deadlocks や ThreadSanitizer、parking_lot:deadlock など
ロックの取得順番を定めた「ロック階層」を設計して、ドキュメント化し、テストし、厳格に運用しよう
ただし、「エンジニアがミスをしないことを前提とする戦略」は長期的に失敗する ため、最後の手段とすべき
マルチスレッドコードでは、「複雑すぎて間違っていることが明らかでないコードではなく、簡単すぎて明らかに間違っていないことが分かるコードを書こう」
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目