rust: std::sync::Mutex と tokio::sync::Mutex
Arc で共有
Rustで、リソースが 1つしかないのに関数が参照ではなく所有権を求めてくることがしばしばある。
そうでなくても、参照で渡すと途端にライフタイムの話が出てきて、これが構造体のメンバーにしたい場合だったりするともうどうしたもんだか。
どうやれっていうんだー、というときに使えるのが Box<T> だった。
- [ヒープのデータを指すBox
を使用する - The Rust Programming Language 日本語版](https://doc.rust-jp.rs/book-ja/ch15-01-box.html)
これはこれでよいのだが、データベースをオープンしてインスタンスを使いまわしたい、というような要望もある。
調べて出てきたのが Arc<T> だった。
元が Rc<T> で、これに atomic な参照カウンタがついたのが Arc<T> らしい。
- [Arc
で原子的な参照カウント](https://doc.rust-jp.rs/book-ja/ch16-03-shared-state.html#arct%E3%81%A7%E5%8E%9F%E5%AD%90%E7%9A%84%E3%81%AA%E5%8F%82%E7%85%A7%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88)
Arc と Mutex
atomic なのは参照カウントに対してだけでアクセスについては特にないようだ。
なのでそのリンク先でも Mutex とセットにした Arc<Mutex<T>> という形で使われている。
使うたびに .lock() で握らないといけないのがちょっと面倒だが、そのくらいで済む。
非同期でエラーになる
そうやってコードを書いていたのだが、async / await が絡むところでエラーになってしまった。
「Send のトレイト境界を満たしていない」みたいな内容だ。
ほかにも同じような関数を書いていて async 関数には最初からなっていたのだ。
それに .await していてもエラーになっていない。
その .await するメソッドを持っている変数を Arc<Mutex<T>> にしたのである。
そういう感じの関数がたくさんあって String でのリクエストによって呼び変えたいだけだったので、
HashMap<String, 関数> みたいな感じにしたくてゴニョゴニョ書いていたのだが、
.lock() に書き換えた関数ではなく HashMap<> に登録するところでエラーになっていた。
まだ慣れてなくて、書き換えた箇所とエラー箇所が離れていると途端にわからなくなるのよね。。。
1時間ほど悩んで Gemini氏に聞いた。質問の仕方がわからないのでまぬけな書き方になった。
Rustで書いたコードがあり、変数の型をTから Arc<Mutex
>に変更し、.lock()したあと .await する関数を作ったところ、Send を満たしていないといわれるようになった。
幸いわかってくれたようだ。
問題の原因: std::sync::MutexGuard は Send ではない
.lock() して握った変数は T ではなく MutexGuard<'_, _T> のような型になっている。
この MutexGuard は Send を実装しておらず、結果としてメソッドの属性?として Send が外れてしまってエラーになったということか。
tokio::sync::Mutex
対策として2案提示された。
1つは tokio::sync::Mutex への変更、もう1つは .lock() の解除後に .await を呼ぶというものだ。
後者は無理だったので Mutex の置き換えを行った。
std::sync::Mutex だったのを tokio::sync::Mutex にするだけだ。
Arc はそのまま std::sync::Arc でよい(tokio::syncに Arc はない)。
tokio::sync::Mutex は .lock() だけでなく .await もいる。
また std::sync::Mutex.lock() と違って戻り値は Result 系ではない。
マルチスレッドかどうかはわからないけど tokio のタスク管理で制御できるから二重ロックの心配をしなくてよいということかな?