Rust の 非同期処理についての覚え書き - future/async/await
対象
内容
前回に続いて、rust の非同期処理について。何をやっているのか理解をするのに結構時間取られたので、サンプルコードと一緒に覚え書きを残しておく。間違ってたらごめんなさい。
async/await/future と、ランタイムと言われる tokio とかって、結局何をやっているの?ってことで、それっぽいものを自作してみた。github のコードはこちら。
- src/epoll_timer.rs は、前に作った libc::epoll を使ったタイマーで、比較のためにこちらにも置いておいたもの
- src/tokio_samples.rs は、tokio を使った簡単な例を幾つか
- src/myfuture.rs が、async/await/future/runtime を模してそれっぽく動くようにしたもの
では、すごく簡単な例を。
// async makes Future
async fn func(n: i32) {
println!("Hello, async: {}!", n);
}
#[tokio::main]
async fn main() {
func(0).await; // .await is done for Future
func(1).await;
}"Hello, async: 0!" と、"Hello, async: 1!" が連続して表示されるだけ。でもこれが大事。
#[tokio::main] でランタイムを作っています。コードから見ると部分的な影響しかないように見えますが、さにあらず。以下全てのコードを tokio のループの中で動かしている。
つまり、上のコードの、func(0).await; と func(1).await; の間には沢山のコード処理が入っている。そして全体がtokio のループの中で処理されている。逆に言うと、ループとかタスクのスケジュールとかを考えなくて良いように、作ってくれている。.await の後はランタイムのメインループに戻っている。
ランタイムでメインループを作って、タスクを登録したり実行したり途中でメインループに戻らせたり、なんてことをしている。お抱え構造。src/tokio_sample.rs にすごく簡単な例を幾つか出しておいた。async キーワードを使わない自作 Future の例とか、async が何をやっているのかの一端が見えるかと。
で本題のサンプル。src/myfuture.rs に、非常に簡単な自作ランタイムと async/await/future らを模したコードを書いておいた。await は普通には実装出来ないと思ったので、タスクを Vector に入れて処理。実際に tokio とかでやってるのは、future の中から子供の future を呼んだりするらしいけど、簡単のために親の処理だけにしてる。
thread_local!{}は、スレッドに固有のデータを溜めて置けるらしくグローバル変数でもいいのだけど、AI に聞いたら tokio とかではこれ使ってるってことだったので、使わせてもらいました。結局ここに保存したランタイムを元に全体の制御をしている。
250行あるけれど、面倒く見えるのは libc 関連で、他はなーんだって感じでは。
突き詰めると、ループの中でタスクキューの中からタスクを出し入れしながら、状態をチェックしているだけ。最初のループでタスクを登録又は実行してから、タスクキューを空にして、登録したものが復帰したらまたタスクを登録するってのは、ちょっとポイントかも。サンプルはタスクの中で複数のコードをVector で抱えるように作りましたのでちょっと変だけど、流れは分かるのではないかと。
tokio とかを使っていると、対象箇所にしか影響がなさそうに見えるけども、実は全部包んでいているのが理解のポイントかもしれない。実行はランタイム。他のはそれへの入口の整備。
rust の非同期処理、色々調べてみても結局どういうことなのかなかなか分かりませんで。自分で模したものを作ってやっとこさ理解出来たのかな?という所。「え、状態マシンを作る?」=> なんだよそう言うことかよ!でしたとさ。