🙆
[Rust]RwLockの概要
はじめに
Rustの標準ライブラリにあるRwLockは、リーダー/ライターロックを提供する同期プリミティブです。多くのリーダーが同時にアクセスできる一方、ライターは独占的なアクセスを持ちます。
特徴:
複数のスレッドが読み取り専用で同時にロックを取得できる。
書き込みアクセス時には、一度に一つのスレッドだけがロックを取得できる。
読み取り中のスレッドが存在する間は、書き込みアクセスがブロックされる。(ここに関しては後述します)
主なメソッド:
read(): 読み取り用ロックを取得する。
write(): 書き込み用ロックを取得する。
サンプルコード
以下は、RwLockを使ったシンプルなサンプルコードです。
use std::sync::{RwLock, Arc};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(0));
// 複数のリーダースレッドを生成
let mut readers = vec![];
for _ in 0..3 {
let data_clone = Arc::clone(&data);
readers.push(thread::spawn(move || {
let read_guard = data_clone.read().unwrap();
println!("Reader: {}", *read_guard);
}));
}
// ライタースレッドを生成
let writer = {
let data_clone = Arc::clone(&data);
thread::spawn(move || {
let mut write_guard = data_clone.write().unwrap();
*write_guard += 1;
println!("Writer: {}", *write_guard);
})
};
// スレッドの終了を待つ
for reader in readers {
reader.join().unwrap();
}
writer.join().unwrap();
}
このサンプルコードでは、RwLockを使って整数データを保護します。3つのリーダースレッドはデータを読み取り、1つのライタースレッドはデータに1を加算します。
デッドロックになるパターン
読み取り中のスレッドが存在する間は、書き込みアクセスがブロックされる。
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(0));
let t = thread::spawn(move || {
let d = data.read().unwrap();
*data.write().unwrap() = 1;
println!("never reach here");
});
t.join().unwrap();
}
dataが読み取り専用でロックされているため、writeができず、ずっとwaitしたままになりデッドロックとなります。
Rustではスコープを抜ける、変数のライフタイムが終了すればロックが自動的に外れるので、以下のように書き換えることでデッドロックを回避できます。
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(0));
let t = thread::spawn(move || {
let d = *data.read().unwrap();
println!("end life time of data: {}", d); // ここでdのライフタイムが終了し、ロックが外れる
*data.write().unwrap() = 1;
println!("reach here");
});
t.join().unwrap();
}
Rustならではの書き方って感じがしますが、テクニックとしては使われてそうですね(あまりライフタイム慣れてないので混乱しそうですが)
まとめ
デッドロックのところはなんかコンパイラでデッドロックになりそうとか分かれば良いんですけどね、、
Discussion