非同期処理によるAPIリクエストのサンプル実装を通して、Fiberが求められてきた背景を説明します。最終的に、それがEventLoopにつながっていきます。
EventLoopの実装から考えるFiberPHPerKaigi petit - PHP8.1リリース祝賀会2021-12-21@hanhan1978
View Slide
自己紹介
こんなのを PHPカンファレンス2021でやった。https://speakerdeck.com/hanhan1978/php-async-programming
ペチコンの内容を3行でまとめると...非同期処理はわからんFiberよくわからんとりあえず、PHPでPHPを動かせ全体的に、分かってるやつしか分からん構成になってしまって反省している。
8.1リリース祝賀会バージョンでは...Fiber完全に理解している非同期処理を完全に理解しているわかりやすく説明できる自信がある
Fiberを理解する必要はあるか?よくある解説これがイヤな場合-> Fiberを理解しないといかん。これでOKな場合-> Fiberを実行基盤にもつフレームワークが登場した時に詰む->つまり、Fiberを理解しないといかん。なんとなく非同期処理ができるっぽいやつ。ライブラリ作成者の為の機能、一般開発者は気にしなくて良い。““
まじめな話PHPファンデーションとかもできたし、実装の詳細から逃げ回っているだけでは駄目。みんなで知見をため、公表しあって、日本からもコミッターが出るように盛り上げていかないとイカン。今日一番まじめな話、終わり。
今日のゴールFiberをみんなが完全に理解すること
物語の開始地点なぜ、Fiberが求められているのか?なぜ、それがPHPに入ってくるのか?この物語のWHYが解決しないと、以降の議論は全て薄くなる。非同期処理が2021年現在、非同期処理から遠い位置にいるPHPにまで言語機能として入ってこようとしているのはなぜ?
半導体の話ムーアの法則が、頭打ちしつつある。電気的に、熱的に、集積度を上げにくくなってる。->コアの性能アップは難しい->だから、マルチコアで並行処理をすることで、スループットを上げよう。ここが開始地点。パタヘネを読もう。
並行処理の話これまでは、我々が気にする必要がなかった。->アプリケーションレイヤーの話ではなく、ハードウェアやOSの役目我々は同期処理パラダイスで生きていけた。
もったいないHTTPリクエストの応答待ち、DBクエリの応答待ち。この間、CPUは遊んでる。厳密には、遊んでるわけではないが、PHPのスクリプトは同期的処理を行うため、きっちり応答をまつ。結果として、CPUはその性能を全て引き出されていない状態になる。
もったいない
もったいなくない
もったいないサンプルdummy API (リクエストにきっちり1秒かかる)
古き良きPHPスクリプト (APIを5回叩く)
同期的HTTPリクエストの結果
なぜ、5秒かかるのか?1 + 1 + 1 + 1 + 1 = 5だから。PHPの組み込み関数はブロッキング。つまり、処理をブロックするHTTP Request ->応答待ちで1秒待つ解消するためには、応答を待たずに次のLoopに入らないといけない。HTTP Request ->応答を待たずにLoop ->結果が帰ってきたら出力
じゃあどうすればいい?file_get_contentsは処理をブロックする... curlでも一緒...どうすれば、処理をブロックしないのか?誰がブロックするのか?
ブロックしていたのは誰?Socketブロッキング、ノンブロッキングのモードを持つ。つまり、ノンブロッキングモードのSocketを使う file_get_contentsを作ればいい。
ブロックしない file_get_contents
PHPによるブロックしないAPIコール
PHPによるブロックしないAPIコール結果ブロックしない、非同期プログミングが完了。
NonBlocking化による実行スレッドの流れ夢は達成された。Generatorも Fiberもいらなかったんや...
本当か?それを確かめるために、調査隊はさきほどのソースコードをGeneratorを使って書き直してみた。
非同期関数のGenerator版
PHPによるブロックしないAPIコール(Generator版)
通信結果を受け取る関数(Generator版)※まじで酷いコードだが、検証用なので許して。
Generator版の実行結果きっちり1秒で5リクエスト
Generatorにしたことで何が起きた?結果を受け取る処理が、g->current()に置き換わった。Generatorで停止したスレッドの再開 という形で抽象化された。
Generator前のコードSocketから結果を受け取るという具象コードを書いている。Generatorを使うと 停止・再開 という文脈を活かした抽象化が出来る。
Generator化による実行スレッドの流れ夢は達成された。 Fiberはいらなかったんや...
本当か?それを確かめるために、調査隊はさきほどのソースコードをFiberを使って書き直してみた。
PHPによるブロックしないAPIコール(Fiber版)
通信結果を受け取る関数(Fiber版)※Generatorを返す必要が無くなった
通信結果を受け取る関数2(Fiber版)
Fiber版の実行結果きっちり1秒で5リクエスト
Fiberにしたことで何が起きた?停止スレッドは Fiberオブジェクトに抽象化Fiberオブジェクトの処理内部では、どこからでも自分を停止できる。Generatorと違って、 yieldして主スレッドに戻す必要がない。
Fiber化による実行スレッドの流れここまでのまとめ
個人的にポイントになると思うところ非同期プログミングにおいては、主スレッドと分岐スレッドという考え方を常に持つ必要がある。今回の例だと、HTTPリクエストを投げているのは分岐スレッド分岐スレッドがレスポンスが受け取ったことを検査して、分岐スレッドの停止・再開を行うのは主スレッド非同期プログミングは、分岐スレッドを管理する主スレッドが必要
EventLoopとは?Swoole, ReactPHP, AmPHPなどは、それぞれ EventLoopを実装している。なぜ、彼らは EventLoopを実装する?EventLoopとは??並行処理をするのに、何故 EventLoopが必要なのか?
EventLoop語弊を恐れずにいうと、ただのLoopさっきのサンプルのFiber版では、Fiberオブジェクトを生成してLoopで回して処理をしていた。これも広義のEventLoopといえる(はず)
EventLoopの構成要素EventProvider処理を Queueに追加EventLoopスケジューラーに従って Eventを 処理EventHandler Eventを実行する
EventLoopの構成要素EventLoopはシングルスレッドでいいリクエストは Queueで一直線にされる (DeMultiplex)Reactor Patternという
EventLoopと Fiberの関係Fiberにより、停止・再開が抽象化されると、処理が抽象的に、簡潔にかけるようになる。これが、Fiberの本当の意味。
Fiberの中身ucontextというライブラリが使われている。このシステムコールは、ユーザーコンテキストの作成・切替をサポートする。boost_contextというやつもある。Swooleは、boost_contextを使って、Fiberっぽいことをやっている。
この先の展開Fiberで実装された EventLoopを持つフレームワークが出てくる。(間違いない)Fiberが拡張され、スケジューラーやPromise, Async/Awaitが PHPに提供される (8.3? 9.0?)
hanhan1978の予定Fiberで EventLoopを実装し、分かりやすいサンプルを書く。EventLoopの構成要素を、分かりやすいサンプルで書く。分かりやすい図で、下の概念を説明する。スケジューラーnon-preemptive非同期、並行、このあたりはOS周りの実装で出てくる。OS実装の本とかに参考になる記述が散見される。
PHPerkaigiにプロポーザル投げるので、みんな星つけてね!