###### tags: `Rust` `lazy_static` `Mutex` `RwLock` `sync`
# Mutex, RwLock でスレッド間リソース共有(グローバル変数も)
# リソースについて
別のスレッド(関数)があるリソースを参照しているときに、
リソースを変更しようとすると、値に整合性が取れなくなる場合がある。
そこで、
- 誰かがwriteしているとき、他のスレッドにread/writeさせない
- 誰かがreadしているとき、他のスレッドにwriteさせない
というリソースロックの仕組みが Mutex と RwLock
## 別名
他のプログラミング言語ではSemaphoreというのがある
RwLockはSemaphoreと似てる?
# 比較
| | Mutex | RwLock |
| -------- | -------- | -------- |
| 説明 | リソースの利用者は、 読み書き関わらず1人だけ | 書き込みは1人 読み込みだけ複数人に許す (Rustの借用ルールに似ている) |
| & | 1 | multi |
| &mut | 1 | 1 |
| lockメソッドの種類 | .lock() | .read() / .write() |
# Mutex
## new(T)
## mux.lock()
- リソースがロックされていなければ、取得
- 読み書き可能なMutexGuardオブジェクト(リソースへのポインタ)が帰ってくる。
- そのままで受け取ればread only
- mut で受け取れば read write
- リソースがロックされていれば、取得できるまでスレッドをブロッキング
- スレッドをブロックしない mux.try_lock() もある
```rust=
let mux = std::sync::Mutex::new(100);
{
let mut writer = mux.lock().unwrap();
*writer = 200; // Deref して書き込み
}
{
let reader = mux.lock().unwrap();
println!("{}", reader);
}
```
## .get_mut() & .into_inner()
どちらも、信頼できない参照先を返す。
- into_inner() : ポインタの中身を返す
- get_mut() : &mut の参照を返す。
これらを mut で移動すると、実質的にMutexの管理で値を守らないので
スレッドセーフの利点がなくなる。
## is_poisoned() : bool
lockを保持したスレッドがpanic!したかどうか
### 絶対やってはいけないアンチパターン
同じスコープ内で lock を解除しないまま新しいロックを取得しようとすると
簡単にデッドロックできる。
```rust=
let mux = std::sync::Mutex::new(100);
let mut writer = mux.lock().unwrap();
*writer = 200; // Deref して書き込み
let reader = mux.lock().unwrap(); // リソースがunlockされないので無限にループ
println!("{}", reader);
```
# RwLock
Mutexの違いは、lock() / try_lock() メソッドが
- read() / try_read()
- write() / try_write()
の2つに増えたくらいしかない。
```rust=
let rwlock = std::sync::RwLock::new(100);
{
let mut writer = rwlock.write().unwrap();
*writer = 200; // Deref して書き込み
}
{
// 読み込みだけはいくらでもOK
let r1 = rwlock.read().unwrap();
let r2 = rwlock.read().unwrap();
let r3 = rwlock.read().unwrap();
let r4 = rwlock.read().unwrap();
let r5 = rwlock.read().unwrap();
println!("{}, {}, {}, {}, {}", r1, r2, r3, r4, r5);
}
```
# Mutex/RwLock を局所的に使うテクニック
テスト関数などで、グローバルなstaticリソースの書き込みをテストする場合など、
- テストの初期化
- 関数の実行
でそれぞれstaticリソースを操作する。
この際に、lockを取得したままだと関数の実行がうまく行かない場合がある。
mutexのlockは、`{}` スコープを抜ける際に自動で Dropトレイトの実装を
実行してlockを解除する。
**{}により、局所的にリソースロックを掛ける**
# Mutex と RwLock どちらを使うか。
個人的には、
- 読み書きどちらをするのか明示的でわかりやすい
- Readが複数許される
~~という点で、 **RwLockはMutexの上位互換** だと思う。~~
Simple_Redis の client が、RwLockに対応していないっぽい。
Mutexだとエラーにならない。
~~Mutexにしか対応していないようなリソースの型を使うときはMutexを使う。~~
そもそも、simple_redisのクライアントにはSyncトレイトが実装されていないので
ArcもMutexもRwLockも使えない。
# static 変数との親和性
Rustにおいて、 static リソースについては
- 読み込みはできる
- 書き込みはunsafeを使わないとできない
- staticがグローバルな変数故に、複数のスレッドで共有されるため
ということで、 `unsage{}` ブロックを使わないと書き込みできない static変数だが、
Mutex/RwLock がlockメソッドで返す **Guardオブジェクト**は**内部ミュータビリティ**
だと思われるので、 unsafeを使わずに書き込みできる。
データベースへの接続を static変数として、グローバルに使い回す例
```rust=
lazy_static! {
pub static ref CONNECTION: RwLock<redis::Connection> = {
let c = CLIENT.get_connection().unwrap();
RwLock::new(c)
};
}
```
```rust=
fn get_by_name(unique_name: &str) -> Result<User, String> {
// redisのクエリを実行するには、 &mut con つまり書き込み可能な接続が必要
let mut con = CONNECTION.write().unwrap();
let result: Result<String, redis::RedisError> = con.hget(userDict, unique_name);
// let result: Result<String, redis::RedisError> = redis::cmd("hget").arg(userDict).arg(unique_name).query(con); // こちらの形式でもかける
match result {
Ok(json) => Ok(serde_json::from_str(&json).unwrap()),
Err(_) => Err(String::from("failed to get user")),
}
}
```
redis クレートのクエリ実行は、Connectionオブジェクトにコマンドを書き込んで実行しなければならない。
よって、RwLock の write() ロックで &mut な参照を持ってくる。
しかし、static変数の CONNECTIONオブジェクトは mut ではない。
RwLockでラップすることで安全に使えるようになった。