Rustで別スレッドの進捗状況によって処理を分岐させる
Rustで別のスレッドのある処理が完了しているかどうかで、処理を分岐させるパターンについて書きました。別のスレッドの処理の完了を必ず待たなければならない場合はfork joinやchannelなどを使うとよいでしょうが、処理が完了していない場合もすぐになんらかの処理を行いたいケースを想定しています。
また、この記事の実装は以下のバージョンのRustで動作確認しています。
$ rustc --version
rustc 1.36.0-nightly (73a3a90d2 2019-05-17)
予備知識としてRustのArc 型とWeak型について知っておく必要があるので、少し触れておきます。
Arc
Arcはアトミックな参照カウントのことです。Syncトレイトを実装しており、複数のスレッドで共有できます。downgradeという関連関数を使用すると、Weakポインタを返します(Weakについては後述)。
Weak
Weakは値に対して共同所有権を持たない参照カウントのことです。upgradeというメソッドを使用すると、Option<Rc<T>>を返します。リソースが解放済みであれば、Noneを返します。
Arc型もWeak型も内部的にstrongとweakという値を持っています。strongは上に書いた参照カウンタのことを指しており、参照される箇所の数が増えればカウントアップします。スコープから抜けるなど、参照される箇所の数が減るとカウントダウンします。weakは参照される箇所の数が増えてもカウントアップされることはありません。明示的にWeakポインタを作成したときにカウントアップされます。カウントダウンについてはstrongと同様です。リソースはweakとは無関係で、strongが0になると開放されます。
実装例
この記事の目的である「別のスレッドのある処理が完了しているかどうかで、処理を分岐する」サンプルプログラムは以下のようになります。この実装はCNCFのプロジェクトの1つであるLinkerdを参考にしています。※ 1
spawn
で生成したスレッドの処理が完了していれば「準備完了」、完了していなければ「まだ」とメインスレッドで表示します。※ 2
では、処理を順に説明していきます。
Readiness
はある処理が終わったかを気にするスレッド(今回の例だとメインスレッド)、 Latch
はReadiness
が気にする処理を行うスレッド(今回の例だとspawn
で生成したスレッド)で使用します。Weak型とArc 型に関しては上述したとおりです。
new
は Arc
を生成し、 Weak
に変換してReadiness
に渡しています。 is_ready
はリソースが解放済みかを確認しています。 Latch
の release
が実行されると、リソースが開放されます。
release
は Readiness::new()
の際に作成した空のArc
を破棄しています。
do_something
は 0〜1000のなかのランダムな値
ミリ秒間sleepする処理です。※ 3 そのため、上のプログラムを実行するたびに、release
と is_ready
のタイミングが変わり、表示内容に影響を与えています。
まとめ
可能であれば、別のスレッドのある処理が完了していてほしいが、待ちはせずにすぐに先に処理を進めたい場合に今回のパターンを適用できると思います(ユースケースは少ないかもしれませんが)。
今回、参考にしたLinkerdのロードバランサだと、Admin用のスレッドは Latch
が release
されていない場合は503を返すような実装になっていました。
※ 1 https://github.com/linkerd/linkerd2-proxy/blob/master/src/app/admin/readiness.rs
※ 2 do_something()
が完了していても、 l.release()
が完了していなければ、「まだ」と表示されます。また、メインスレッドの処理が先に完了すると、spawn
で生成したスレッドの処理は途中でも終了します。
※ 3 do_something
の処理自体は本題から逸れるものです。