見出し画像

非同期処理の話❶

こんにちは、株式会社POLでエンジニアをしているミズノです。GWでまとまった時間が取れたので非同期処理を調べてみました。最近では当たり前のように使用していたasync/await、そもそもなぜこれで動くの?と気になってもいました。
調べてみると、「俺は雰囲気で書いていたんだな〜」と自分自身が理解できてなかったと気づけ良い勉強にもなりました。
まだまだわからないこともたくさんありますが。自分の勉強のためにもnoteにまとめていきたいと思います。間違った情報あればご指摘いただけると助かります。

非同期処理と並行

非同期処理はざっくり言えば並行処理です。例えば異なるタスクがあるときに、それらが交互に処理されることを指します。

並行とよく間違えるのが並列です。2つのタスクを同時に処理できるのであれば、それは並列です。プログラミングではスレッドを利用して並列処理を表現しすると思います。

非同期処理は並列ではなく並行の方をさしますが、だからといってスレッド(並列)と切り離されるものではないようです。非同期のランタイムは別スレッドで実行されたりもします。言語に組み込まれているのもは実装を追いかけづらいですが、Rustはコードも追いやすいので、調べるには良い環境です。この後もRustのコードを例に紹介します。

言語毎の非同期処理

よく利用されるプログラミング言語には非同期処理は当たり前のように存在しています。例えばJavaScriptのPromise 、C#のTask、RustではFutureと呼ばれたりします。実装はそれぞれ違いますが、思想は同じと思われます。

Rustでは以下のように非同期処理の対象はFutureトレイトを実装する必要があります。

// 非同期対象
struct Target {}

// 非同期処理
impl Target {
  async fn doSomething(){
    // 省略
  }
}

// pollメソッドを実装する
impl Future for Target {
  fn poll(引数は省略) {}
}

// 
let t = Target{}
let f = t.doSomething(); // doSomething実行して処理が始まるわけではない。

最後の行で非同期処理メソッドを実行してもすぐには実行されません。let f にはFutureオブジェクトが束縛されます。これが同期処理と、非同期処理の違いです。ちなみにここで .awaitを実行すると内部の非同期処理が実行されます。

非同期処理はポーリング

awaitを利用するとFutureトレイトのpollメソッドが実行されます。名前から推測できる通り、poll()はポーリングです。pollメソッドが実行されると非同期処理が実行され、Ready or Pendingが返されます。Pendingは処理が終わってないので、定期的にポーリングをします。定期的にポーリングすることでいつかは処理が終わりReadyが帰ってくるという流れです。ポーリングを頻繁に行うと、パフォーマンスを下げてしまうのでRustではWakerというコールバックオブジェクト経由で、処理が終了が通知される実装が利用されています。それを受けて再度pollメソッドを実行すればreadyが帰ってくるのでパフォーマンスの問題も気になりません。

画像1

こうやって調べてみると、割と想像した通りの流れでした。もちろん突き詰めればシステムコールなど低レイヤーの話も出てきますが、一つづ紐解くとコアはシンプルです。中身の仕組みがわかってくると非同期処理で扱うべきものとそうでないものが具体的にわかるのでとても面白いです。
次回はTokioを例にシンプルな非同期処理のコード例で具体を説明します。

まだ絶賛調べているので、興味ある人はぜひお話ししましょう。Meetyやってるのでぜひ!

https://meety.net/matches/cRWkjvfyJxnA

この記事が気に入ったらサポートをしてみませんか?