Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
並行処理を学びGuzzleと仲良くなる
Search
shimabox
June 21, 2024
Programming
2
900
並行処理を学びGuzzleと仲良くなる
PHPカンファレンス福岡2024の発表資料になります
※ 2024/06/23 加筆/修正しました
shimabox
June 21, 2024
Tweet
Share
More Decks by shimabox
See All by shimabox
擬人化で完全に理解するクリーンアーキテクチャ
shimabox
19
7.9k
Unit of Workパターンで永続化とトランザクションを制御する
shimabox
10
5.6k
PHPDocにおける配列の型定義を少し知る
shimabox
2
3.3k
Other Decks in Programming
See All in Programming
AWS IaCの注目アップデート 2024年10月版
konokenj
3
3.3k
Generative AI Use Cases JP (略称:GenU)奮闘記
hideg
1
290
Tauriでネイティブアプリを作りたい
tsucchinoko
0
370
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
230
【Kaigi on Rails 2024】YOUTRUST スポンサーLT
krpk1900
1
330
TypeScript Graph でコードレビューの心理的障壁を乗り越える
ysk8hori
2
1.1k
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
2024/11/8 関西Kaggler会 2024 #3 / Kaggle Kernel で Gemma 2 × vLLM を動かす。
kohecchi
5
910
Realtime API 入門
riofujimon
0
150
型付き API リクエストを実現するいくつかの手法とその選択 / Typed API Request
euxn23
8
2.2k
Less waste, more joy, and a lot more green: How Quarkus makes Java better
hollycummins
0
100
Amazon Qを使ってIaCを触ろう!
maruto
0
400
Featured
See All Featured
Writing Fast Ruby
sferik
627
61k
Thoughts on Productivity
jonyablonski
67
4.3k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
131
33k
Optimising Largest Contentful Paint
csswizardry
33
2.9k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Building an army of robots
kneath
302
43k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
44
2.2k
Reflections from 52 weeks, 52 projects
jeffersonlam
346
20k
Documentation Writing (for coders)
carmenintech
65
4.4k
A better future with KSS
kneath
238
17k
Producing Creativity
orderedlist
PRO
341
39k
Transcript
並行処理を学び Guzzleと仲良くなる 2024/06/22 ペチコン福岡2024 by しまぶ@shimabox
自己紹介
はじめに • これからお話しする並行/並列処理に関することは、これらの本を(少し)読 んで得た自分なりの理解に基づいています Go言語 100Tips ありがちなミスを把握し、実装を最適化する https://www.amazon.co.jp/gp/product/4295017531 並行プログラミング入門 ―Rust、C、アセンブリによる実装か
らのアプローチ https://www.amazon.co.jp/dp/4873119596
1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ アジェンダ
1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ アジェンダ
1. 並行処理を学ぶ • プロセス / スレッド • 並行処理 / 並列処理
• 非同期処理 並行処理を学ぶその前にもろもろ整理
1. 並行処理を学ぶ • プロセスはプログラムの実行の単位 • 各プロセスは独立したメモリ空間を持ち、他のプ ロセスとはメモリを共有しない • 各プロセスは独自のリソース(ファイルハンド ル、データ、コード)を持つ
プロセス とは
1. 並行処理を学ぶ • 複数のプロセス → マルチプロセス • ひとつのプロセス → シングルプロセス
• 生成には比較的大きなオーバーヘッドが伴う プロセス とは
1. 並行処理を学ぶ プロセス とは • 大きく分けて以下の4つの状態を取る • 実行前状態 • 実行状態
• 待機状態 • 終了状態
1. 並行処理を学ぶ プロセス とは P2 図1-1 プロセスの状態遷移図
1. 並行処理を学ぶ プロセス とは 待機状態へ遷移する理由 • データの到着を待つため ◦ 処理対象が無ければ無意味 •
リソースの空きを待つため ◦ 計算できる道具が利用され ていたら待つ • 自発的に待機状態となるため ◦ タイマー処理とか ◦ リソースを専有しない
1. 並行処理を学ぶ プロセス とは • プロセスは実行状態と待 機状態への遷移を繰り返 しながら処理を進めてい く
1. 並行処理を学ぶ プロセス とは P3 図1-2 あるプロセスの状態遷移と計算途中状態より抜粋
1. 並行処理を学ぶ • プロセス内で実行される軽量な実行単位 • スレッドはプロセス内のリソースを共有 ◦ 例:ファイルハンドル、データ ◦ メモリ空間を共有する
◦ データの競合が発生する可能性がある • 生成のオーバーヘッドはプロセスに比べて小さい • スレッド間のコンテキストスイッチも比較的高速 スレッド とは
1. 並行処理を学ぶ • アプリケーションを起動すると1つ(あるいは少 数)のプロセスが生成されて、プロセス内で複数 のスレッドが生成される → マルチスレッド • 1つのプロセスが1つのスレッドしか持たない
→ シングルスレッド • プログラム内で実行される処理は`タスク`と呼ん だりする(プロセス、スレッドひっくるめて) スレッド とは
1. 並行処理を学ぶ プロセス / スレッド P4 図1-4 OSプロセスとスレッド
1. 並行処理を学ぶ プロセス / スレッド プロセスやスレッドによって実行される個々の処理 の集まりを `タスク` と呼んだりする
1. 並行処理を学ぶ プロセス / スレッド • プロセスは別プロセスと会話できない ◦ (基本的には。IPCをググれ。) •
スレッドは同じプロセス内で会話できる ❌(基本的に) ❌ ◯
1. 並行処理を学ぶ • 一つのCPU,コアが複数のタスクを短い時間ごと に切り替えながら実行する ◦ プロセスを切り替えたり ◦ マルチスレッドを使い高速に切り替えたり •
タスクが「同時に」進行しているように見えるが 実際には交互に実行されている • I/Oバウンドなタスクに有効 ◦ I/O操作の待機中に他のタスクを進行させることで、 待ち時間を有効に活用する 並行処理(Concurrency) とは
1. 並行処理を学ぶ 並行処理(Concurrency) とは P3 図1-2 あるプロセスの状態遷移と計算途中状態
1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考
1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 同じコアで 動いている
プロセス
1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 ここを高速に 切り替えている
1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 同じプロセス内 で動いている
スレッド
1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド
1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド • プロセスは分かれてい るから安定 •
プロセスの生成、コンテ キストスイッチのオー バーヘッドのコストが大 きい • コンテキストスイッチの コストは小さい • スレッドセーフを考える 必要がある • プロセスでも共有リソー スを扱う場合注意は必 要(ファイルとか)
1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド • マルチプロセス x マルチスレッド
というのももちろんある • コンテキストスイッチの コストは小さい • スレッドセーフを考える 必要がある • プロセスでも共有リソー スを扱う場合注意は必 要(ファイルとか)
1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド • マルチプロセス x マルチスレッド
というのももちろんある • マルチスレッドプログラ ミングとか恐怖でしかな いな
1. 並行処理を学ぶ • 複数のCPUまたはコア(マルチコア)がそれぞれ異 なるタスクを同時に実行する ◦ コアが一つしかない場合(シングルコア)、 真の並列処理はできない • 物理的に同時に実行される
• CPUバウンドなタスク(計算/画像処理)に適して いる ◦ CPUリソースを最大限に活用 並列処理(Parallelism) とは
1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-3 あるプロセスAとBの実行状態と並行性
1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 違うコアで 動いている タスク
1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-5 あるプロセスAとBの実行状態と並列性
1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-5 あるプロセスAとBの実行状態と並列性 実行状態が被っ ているところ 違うコアで
動いている タスク
1. 並行処理を学ぶ 並行処理 / 並列処理 • 並行処理は並列処理を包 含する ◦ 並行処理が並列処理を可能
にする • “並行処理とは、一度に多 くを扱うことです。 並列処理とは、一度に多 くを行うことです。” - Rob Pike
1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ここにコンビニがあるじゃろ
1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ここにコンビニがあるじゃろ CPU
1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える そこにはよくある風景があるじゃろ
1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える そこにはよくある風景があるじゃろ 待ち プロセス スレッド
プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ひとつひとつ捌いていたら時間がかかる スレッド
プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 逐次処理 スレッド
プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 処理するもの(スレッド)を増やす スレッド
プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 店員さんがレジや電子レンジを駆使して処理をする スレッド
プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 並行 スレッド
待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える これも並行
CPU 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 並列 CPU
1. 並行処理を学ぶ • 注意したいこと ◦ プロセス/スレッド/並行/並列 といろいろ出て きたが、それぞれが独立した単語 ◦ プロセス/スレッドは実行単位を指す
▪ マルチ/シングルがさらにある ◦ 並行/並列は実行状態を指す ◦ 並列はマルチコアのみ 並行処理 / 並列処理
1. 並行処理を学ぶ • 注意したいこと ◦ プロセス/スレッド/並行/並列 といろいろ出て きたが、それぞれが独立した単語 ◦ プロセス/スレッドは実行単位を指す
▪ マルチ/シングルがさらにある ◦ 並行/並列は実行状態を指す ◦ 並列はマルチコアのみ 並行処理 / 並列処理 混ぜるな危険 😇
1. 並行処理を学ぶ • 注意したいこと ◦ 並行/並列にしたから といって必ず早くなる とは限らない ◦ オーバーヘッドはある
◦ アムダールの法則とい うのもあってだな 並行処理 / 並列処理 P8 図1-6 並列化による高速化の例
1. 並行処理を学ぶ • シングルスレッドでしか動かないものは並行処理 できないの? シングルスレッドは?
1. 並行処理を学ぶ • シングルスレッドでしか動かないものは並行処理 できないの? • キューを使う ◦ ワーカーを立ち上げて別プロセスで処理をさせる シングルスレッドは?
1. 並行処理を学ぶ • シングルスレッドでしか動かないものは並行処理 できないの? • キューを使う ◦ ワーカーを立ち上げて別プロセスで処理をさせる •
非同期I/Oというのもある シングルスレッドは?
1. 並行処理を学ぶ • ファイル操作、ネットワーク通信(HTTPリクエス ト/ソケット)、データベースアクセスなど、すべ てのI/O操作を非同期に実行する • たとえば、JS,Node.js はシングルスレッドで動 作するが、イベントループという仕組みを使って
非同期I/Oを実現している 非同期I/O とは?
1. 並行処理を学ぶ • 非同期I/Oは、I/O操作を開始しその完了を待つ間 に他の処理を進める仕組み • イベントループがI/Oの完了を監視し、完了時に コールバック関数を実行する • これにより待ち時間が最小化される
• C10K問題もうんたらかんたら 非同期I/O とは?
1. 並行処理を学ぶ イベントループ とは? • 非同期タスクの登録 ◦ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される •
タスクキュー ◦ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ◦ たとえばPromise(成功か失敗のどちらかを保持している) • イベントループの実行 ◦ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ◦ ループしながらイベントを監視しているイメージ
1. 並行処理を学ぶ イベントループ とは? • 非同期タスクの登録 ◦ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される •
タスクキュー ◦ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ◦ たとえばPromise(成功か失敗のどちらかを保持している) • イベントループの実行 ◦ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ◦ イベントをループしながら監視しているイメージ 同期的なプログラミング言語の視点から非同期処理を理解する https://speakerdeck.com/hanhan1978/understand-async-from-sync
1. 並行処理を学ぶ イベントループ とは? • 非同期タスクの登録 ◦ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される •
タスクキュー ◦ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ◦ たとえばPromise(成功か失敗のどちらかを保持している) • イベントループの実行 ◦ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ◦ イベントをループしながら監視しているイメージ 同期的なプログラミング言語の視点から非同期処理を理解する https://speakerdeck.com/hanhan1978/understand-async-from-sync 神(所)
1. 並行処理を学ぶ 並行処理(その他もろもろ) 完全に理解しましたね?
2. Guzzleと仲良くなる 1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ
Guzzleと仲良くなっていきたいの ですが 2. Guzzleと仲良くなる
ここまで聞いて 何かソワソワしている人が いるかもしれませんね 2. Guzzleと仲良くなる
PHP 2. Guzzleと仲良くなる
そう、PHPは シングルスレッドモデルです 2. Guzzleと仲良くなる
先に説明したような並行/並列処理 は直接サポートしていない (ライブラリや拡張モジュールが別途必要) 2. Guzzleと仲良くなる
Guzzleでググると 並行処理だとか並列処理だとか 聞くがどうやっているのか 2. Guzzleと仲良くなる
2. Guzzleと仲良くなる PHPのHTTPクライアントでHTTPリクエストを簡単に送信でき、以下 の特徴があります。 • 同じインターフェースで同期・非同期リクエストの送信が可能 • PSR-7およびPSR-18をサポートしている • 基本的なHTTPトランスポートを抽象化(cURL、PHPストリーム、
ソケット、非ブロッキングイベントループなどへの依存なし) • ミドルウェアシステムにより、クライアントの動作をカスタマイズ 可能 これらの機能により、複雑なHTTP操作を簡単に実行できます。 Guzzle https://github.com/guzzle/guzzle
2. Guzzleと仲良くなる PHPのHTTPクライアントでHTTPリクエストを簡単に送信でき、以下 の特徴があります。 • 同じインターフェースで同期・非同期リクエストの送信が可能 • PSR-7およびPSR-18をサポートしている • 基本的なHTTPトランスポートを抽象化(cURL、PHPストリーム、
ソケット、非ブロッキングイベントループなどへの依存なし) • ミドルウェアシステムにより、クライアントの動作をカスタマイズ 可能 これらの機能により、複雑なHTTP操作を簡単に実行できます。 Guzzle https://github.com/guzzle/guzzle 一言も並行処理だとか謳っていない 😇
2. Guzzleと仲良くなる • 同じインターフェースで同期・非同期リクエスト の送信が可能 Guzzleは非同期リクエストをサポートしており、こ れにより並行的に複数のHTTPリクエストを処理す ることが可能。 Guzzle https://github.com/guzzle/guzzle
2. Guzzleと仲良くなる • 非同期I/Oと似ているが、 特にネットワーク通信 (特にHTTPリクエスト) に焦点を当てた非同期処 理 • リクエストを非同期に送
信し、プロミスで管理し て、完了時にコールバッ クで処理 非同期リクエスト とは
2. Guzzleと仲良くなる • 素?のGuzzleは並行処理も非同期I/Oもサポート していない ◦ イベントループの実装もない • 並行、非同期I/Oを行うには拡張モジュールやラ イブラリが別途必要
Guzzle https://github.com/guzzle/guzzle
2. Guzzleと仲良くなる • 拡張モジュール ◦ pthreads ▪ マルチスレッドの機能を提供する拡張 モジュール ▪
https://github.com/krakjoe/pthreads ◦ parallel ▪ マルチスレッドの並行処理を提供する 拡張モジュール ▪ https://github.com/krakjoe/parallel ◦ Swoole ▪ 非同期I/Oや並行処理をサポート ▪ https://www.swoole.co.uk/ ◦ PCNTL ▪ プロセス制御を提供する ▪ https://www.php.net/manual/ja/boo k.pcntl.php Guzzle https://github.com/guzzle/guzzle • ライブラリ ◦ ReactPHP ▪ 非同期I/Oを提供するライブラリで、 並行処理をサポート ▪ https://reactphp.org/ ◦ Amp ▪ 非同期I/Oをサポートするライブラリ で、コルーチンを使用して非同期プロ グラミングをサポート ▪ https://amphp.org/
2. Guzzleと仲良くなる • 拡張モジュール ◦ pthreads ▪ マルチスレッドの機能を提供する拡張 モジュール ▪
https://github.com/krakjoe/pthreads ◦ parallel ▪ マルチスレッドの並行処理を提供する 拡張モジュール ▪ https://github.com/krakjoe/parallel ◦ Swoole ▪ 非同期I/Oや並行処理をサポート ▪ https://www.swoole.co.uk/ Guzzle https://github.com/guzzle/guzzle • ライブラリ ◦ ReactPHP ▪ 非同期I/Oを提供するライブラリで、 並行処理をサポート ▪ https://reactphp.org/ ◦ Amp ▪ 非同期I/Oをサポートするライブラリ で、コルーチンを使用して非同期プロ グラミングをサポート ▪ https://amphp.org/ これらと組み合わせる or おとなしくそれを使う
申し訳ないですが、 素のGuzzleと仲良くなります 2. Guzzleと仲良くなる
2. Guzzleと仲良くなる Guzzle https://github.com/guzzle/guzzle • Composerでサクッといれられる(7系入れてこ?) ◦ composer require guzzlehttp/guzzle
2. Guzzleと仲良くなる Guzzle https://github.com/guzzle/guzzle • バージョンは以下の通り (7.8.1) ◦ $ composer
show guzzlehttp/guzzle name : guzzlehttp/guzzle descrip. : Guzzle is a PHP HTTP client library keywords : client, curl, framework, http, http client, psr-18, psr-7, rest, web service versions : * 7.8.1 ~
2. Guzzleと仲良くなる Guzzle サンプルその1(同期リクエスト) <?php require_once __DIR__ . '/../vendor/autoload.php'; use
GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; $client = new Client(); $res = $client->sendRequest(new Request('GET', 'http://localhost/sample/1')); echo $res->getBody();
2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) <?php use Illuminate\Support\Facades\Route; Route::get('/sample/{id}', function ($id)
{ sleep(1); return response()->json([ 'id' => $id, ]); }); <?php $client = new Client(); $start = hrtime(true); $client->sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL;
2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) <?php use Illuminate\Support\Facades\Route; Route::get('/sample/{id}', function ($id)
{ sleep(1); return response()->json([ 'id' => $id, ]); }); <?php $client = new Client(); $start = hrtime(true); $client->sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; $ php sample2.php 3.629311876 秒
2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) <?php use Illuminate\Support\Facades\Route; Route::get('/sample/{id}', function ($id)
{ sleep(1); return response()->json([ 'id' => $id, ]); }); <?php $client = new Client(); $start = hrtime(true); $client->sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; 順序に依存関係のない処理であれば、 もったいない🤔
2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) <?php use Illuminate\Support\Facades\Route; Route::get('/sample/{id}', function ($id)
{ sleep(1); return response()->json([ 'id' => $id, ]); }); <?php $client = new Client(); $start = hrtime(true); $client->sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; 順序に依存関係のない処理であれば、 もったいない🤔
2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) <?php use Illuminate\Support\Facades\Route; Route::get('/sample/{id}', function ($id)
{ sleep(1); return response()->json([ 'id' => $id, ]); }); <?php $client = new Client(); $start = hrtime(true); $client->sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; イメージとしてはこうしたい
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) <?php require_once __DIR__ . '/../vendor/autoload.php'; use
GuzzleHttp\Client; use GuzzleHttp\Promise\Utils; use GuzzleHttp\Promise\PromiseInterface; $client = new Client(); $requests = [ 'req1' => $client->requestAsync('GET', '略'), 'req2' => $client->requestAsync('GET', '略'), 'req3' => $client->requestAsync('GET', '略'), ]; $results = Utils::settle($requests)->wait(); foreach ($results as $key => $result) { if ($result['state'] === PromiseInterface::FULFILLED ) { echo "$key success" . PHP_EOL; } else { // こっちは、PromiseInterface::REJECTED echo "$key failed" . PHP_EOL; } }
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) $client = new Client(); // リクエストを用意
$requests = [ // $client->getAsync('http://localhost/sample/1') でもよいが // GuzzleHttp\ClientInterface で requestAsync が // 定義されているので扱いやすい 'req1' => $client->requestAsync('GET', '略'), ]; // Utils::settle() で GuzzleHttp\Promise\PromiseInterface が返る(Promiseってやつ) // PromiseInterface の wait() を呼ばないと処理は実行されない(Promiseは解決されない) $results = Utils::settle($requests)->wait(); // $resultsはPromiseが解決されているので後は好きにしてもろうて
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) <?php require_once __DIR__ . '/../vendor/autoload.php'; use
GuzzleHttp\Client; use GuzzleHttp\Promise\Utils; use GuzzleHttp\Promise\PromiseInterface; $client = new Client(); $requests = [ 'req1' => $client->requestAsync('GET', '略'), 'req2' => $client->requestAsync('GET', '略'), 'req3' => $client->requestAsync('GET', '略'), ]; $results = Utils::settle($requests)->wait(); foreach ($results as $key => $result) { if ($result['state'] === PromiseInterface::FULFILLED ) { echo "$key success" . PHP_EOL; } else { // こっちは、PromiseInterface::REJECTED echo "$key failed" . PHP_EOL; } } $ php sample3.php 1.2239755 秒 イメージはあってそう
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) Utils::settle($requests) • Promiseの解決準備 • Promise ◦
ざっくりいうとリクエスト が解決されたら、 `成功(fulfilled)` or `失敗(rejected)` を返すもの • wait() ◦ Promiseを解決するもの $client = new Client(); // ここではリクエストは投げられていない $requests = [ 'req1' => $client->requestAsync('GET', '略'), 'req2' => $client->requestAsync('GET', '略'), 'req3' => $client->requestAsync('GET', '略'), ]; // ここで、Promiseを解決している(リクエストの実行) $results = Utils::settle($requests)->wait(); // 上記は以下のようにも書ける // Promiseの解決準備 $promises = Utils::settle($requests); // Promiseの解決を待つ $results = $promises->wait();
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) $client = new Client(); // ここではリクエストは投げられていない
$requests = [ 'req1' => $client->requestAsync('GET', '略'), 'req2' => $client->requestAsync('GET', '略'), 'req3' => $client->requestAsync('GET', '略'), ]; // ここで、Promiseを解決している(リクエストの実行) $results = Utils::settle($requests)->wait(); // 上記は以下のようにも書ける // Promiseの解決準備 $promises = Utils::settle($requests); // Promiseの解決を待つ $results = $promises->wait(); wait() • 内部で curl_multi を使用して 非同期リクエストを実行 ◦ curl_multi系のメソッドを諸々利 用している • 複数のリクエストが並行して実 行される(I/O多重化) ◦ リクエストが完了するまで監視 • curl_multiはノンブロッキング • リクエストがすべて完了すると Promiseが解決される
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) use GuzzleHttp\Client; use GuzzleHttp\Promise\Utils; use GuzzleHttp\Psr7\Response;
use Psr\Http\Client\ClientExceptionInterface; $client = new Client(); $requests = [ $client->requestAsync('GET', '略')->then( function (Response $response) { // 成功時の処理 echo $response->getBody(); }, function (ClientExceptionInterface $reason) { // 失敗時の処理 // https://docs.guzzlephp.org/en/latest/quickstart.html#exceptions // GuzzleHttp\Exception\ConnectException または、 // GuzzleHttp\Exception\RequestException が実装している echo $reason; }, ), ]; Utils::settle($requests)->wait(); then() • 成功時と失敗時に呼び出される コールバックを定義できる ※ なお、curl_multi はこの記事が 詳しい • curl_multiでHTTP並行リクエストを行 うサンプル https://qiita.com/Hiraku/items/1c6 7b51040246efb4254
2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) use GuzzleHttp\Client; use GuzzleHttp\Promise\Utils; use GuzzleHttp\Psr7\Response;
use Psr\Http\Client\ClientExceptionInterface; $client = new Client(); $requests = [ $client->requestAsync('GET', '略')->then( function (Response $response) { // 成功時の処理 sleep(1); // 処理は止まる }, function (ClientExceptionInterface $reason) { // 失敗時の処理 sleep(1); // 処理は止まる }, ), ]; Utils::settle($requests)->wait(); 注意されたし • HTTPは非同期に投げているが コールバックでブロックされる • すべてが非同期に行われるわけ ではない ※ なお、この記事が詳しい • とまれーっ うごけーっ https://qiita.com/tadsan/items/63b 8d84193498b1c6191
2. Guzzleと仲良くなる 同期処理はどうしているのか? $client->sendRequest(new Request('略')); // こうなっている // https://github.com/guzzle/guzzle/blob/7.8/src/Client.php#L132 public
function sendRequest(RequestInterface $request): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); }
2. Guzzleと仲良くなる 同期処理はどうしているのか? • 中でwait()を使っている • が、ここで使われるのは ◦ curl_exec •
この呼び出しはブロッキング処 理で、リクエストが完了するま で待つ ◦ Promiseを解決していると いえば解決はしている • リクエストが完了したら結果を 返している $client->sendRequest(new Request('略')); public function sendRequest(RequestInterface $request): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); }
• curl_exec と curl_multi はどこで判断しているのか • Proxyというのがあってだな ◦ https://github.com/guzzle/guzzle/blob/7. 8/src/Handler/Proxy.php#L25
• RequestOptions::SYNCHRONOUS が true でsend系メソッドが呼ばれていた ら ◦ Handler\CurlHandler (curl_exec) • false で呼ばれていたら ◦ Handler\CurlMultiHandler (curl_multi) 2. Guzzleと仲良くなる 同期処理はどうしているのか? $client->sendRequest(new Request('略')); public function sendRequest(RequestInterface $request): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); }
• curl_exec と curl_multi はどこで判断しているのか • Proxyというのがあってだな ◦ https://github.com/guzzle/guzzle/blob/7. 8/src/Handler/Proxy.php#L25
• RequestOptions::SYNCHRONOUS が true でsend系メソッドが呼ばれていた ら ◦ Handler\CurlHandler (curl_exec) • false で呼ばれていたら ◦ Handler\CurlMultiHandler (curl_multi) 2. Guzzleと仲良くなる 同期処理はどうしているのか? $client->sendRequest(new Request('略')); public function sendRequest(RequestInterface $request): ResponseInterfac { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); }
• curl_exec と curl_multi はどこで判断しているのか • Proxyというのがあってだな ◦ https://github.com/guzzle/guzzle/blob/7. 8/src/Handler/Proxy.php#L25
• RequestOptions::SYNCHRONOUS が true でsend系メソッドが呼ばれていた ら ◦ Handler\CurlHandler (curl_exec) • false で呼ばれていたら ◦ Handler\CurlMultiHandler (curl_multi) 2. Guzzleと仲良くなる 同期処理はどうしているのか? $client->sendRequest(new Request('略')); public function sendRequest(RequestInterface $request): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); } Handlerを使ってよしなに 処理をしている
• curl_exec と curl_multi はどこで判断しているのか • Proxyというのがあってだな ◦ https://github.com/guzzle/guzzle/blob/7. 8/src/Handler/Proxy.php#L25
• RequestOptions::SYNCHRONOUS が true でsend系メソッドが呼ばれていた ら ◦ Handler\CurlHandler (curl_exec) • false で呼ばれていたら ◦ Handler\CurlMultiHandler (curl_multi) 2. Guzzleと仲良くなる 同期処理はどうしているのか? $client->sendRequest(new Request('略')); public function sendRequest(RequestInterface $request): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; return $this->sendAsync($request, $options)->wait(); } HTTPリクエストをいい感じ に扱いやすいようにラップ してくれているんだね
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) • 非同期にリクエストを投げられるのは分かった • ただ、もう少し柔軟にリクエストを投げたい •
実行数がよめない、大量に実行したいケースとか ◦ 一気にドーンではなく(メモリも気になるし) • たとえば100個のリクエストを同時に投げるので はなく、10個ずつ10回に分けて実行したい場合 • そこで、Poolを使う
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) <?php require_once __DIR__ . '/../vendor/autoload.php';
use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Response; use Psr\Http\Client\ClientExceptionInterface; $client = new Client(); $requests = function ($total) use ($client) { $uri = 'http://localhost/sample/'; for ($i = 1; $i <= $total; $i++) { yield fn() => $client->requestAsync('GET', $uri . $i); } }; $pool = new Pool($client, $requests(100), [ 'concurrency' => 10, // 10個ずつリクエストを投げる(デ フォルト25回) 'fulfilled' => fn(Response $res, $i) => print("{$i} completed.\n"), // 成功時の処理 'rejected' => fn( ClientExceptionInterface $reason, $i ) => print("{$i} failed: {$reason}\n"), // 失敗時の処理 ]); $promise = $pool->promise(); $promise->wait();
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) $client = new Client(); //
Poolから呼び出される // リクエストをGeneratorで返す $requests = function ($total) use ($client) { $uri = 'http://localhost/sample/'; for ($i = 1; $i <= $total; $i++) { yield fn() => $client->requestAsync('GET', $uri . $i); } };
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) // Poolの生成 // 送信するリクエストを管理する //
(EachPromiseでPromiseを管理) $pool = new Pool($client, $requests(100), [ 'concurrency' => 10, // 10個ずつリクエストを投げる(デフォルト25回) // このへんthen()に似てるね 'fulfilled' => fn(Response $res, $i) => print("{$i} completed.\n"), // 成功時の処理 'rejected' => fn( ClientExceptionInterface $reason, $i ) => print("{$i} failed: {$reason}\n"), // 失敗時の処理 ]); $promise = $pool->promise(); $promise->wait();
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) // Poolの生成 // 送信するリクエストを管理する //
(EachPromiseでPromiseを管理) $pool = new Pool($client, $requests(100), [ 'concurrency' => 10, // 10個ずつリクエストを投げる(デフォルト25回) // このへんthen()に似てるね 'fulfilled' => fn(Response $res, $i) => print("{$i} completed.\n"), // 成功時の処理 'rejected' => fn( ClientExceptionInterface $reason, $i ) => print("{$i} failed: {$reason}\n"), // 失敗時の処理 ]); $promise = $pool->promise(); $promise->wait(); レスポンスはリクエストを 投げた順番に返ってこない
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) $params = [ // 文字列が識別子となっているケース
'foo' => ['id' => 1, '...'], 'baz' => ['id' => 2, '...'], ]; $requests = function ($params) use ($client) { $uri = 'http://localhost/sample/'; foreach ($params as $key => $param) { // yield で $key を渡すことにより、コールバックで識別子が使える yield $key => fn() => $client->requestAsync('GET', $uri . $param['id']); } }; $pool = new Pool($client, $requests($params), [ 'concurrency' => 10, 'fulfilled' => fn(Response $res, $key) => print("{$params[$key]['id']}\n"), // yieldで渡した$key 'rejected' => fn(ClientExceptionInterface $reason, $key) => [], ]);
2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) $params = [ // 文字列が識別子となっているケース
'foo' => ['id' => 1, '...'], 'baz' => ['id' => 2, '...'], ]; $requests = function ($params) use ($client) { $uri = 'http://localhost/sample/'; foreach ($params as $key => $param) { // yield で $key を渡すことにより、コールバックで識別子が使える yield $key => fn() => $client->requestAsync('GET', $uri . $param['id']); } }; $pool = new Pool($client, $requests($params), [ 'concurrency' => 10, 'fulfilled' => fn(Response $res, $key) => print("{$params[$key]['id']}\n"), // yieldで渡した$key 'rejected' => fn(ClientExceptionInterface $reason, $key) => [], ]); yield を活用すると捗る
2. Guzzleと仲良くなる Guzzle テストの題材 $client = new Client(); $params =
[ 'foo' => ['id' => 1, '...'], 'baz' => ['id' => 2, '...'], ]; $requests = function ($params) use ($client) { $uri = 'http://localhost/sample/'; foreach ($params as $key => $param) { yield $key => fn() => $client->requestAsync('GET', $uri . $param['id']); } }; $pool = new Pool($client, $requests($params), [ 'concurrency' => 10, 'fulfilled' => fn(Response $res, $key) => print("{$params[$key]['id']}\n"), 'rejected' => fn(ClientExceptionInterface $reason, $key) => [], ]);
2. Guzzleと仲良くなる Guzzle テストの題材 readonly class GuzzleSample public function __construct(
private ClientInterface $client, private ClientPoolFactoryInterface $poolFactory, private FulfilledHandlerInterface $fulfilledHandler, private RejectedHandlerInterface $rejectedHandler, private array $params, private int $concurrency = 10 ) {}
2. Guzzleと仲良くなる Guzzle テストの題材 readonly class GuzzleSample public function __construct(
private ClientInterface $client, private ClientPoolFactoryInterface $poolFactory, private FulfilledHandlerInterface $fulfilledHandler, private RejectedHandlerInterface $rejectedHandler, private array $params, private int $concurrency = 10 ) {} 差し替えられるように
2. Guzzleと仲良くなる Guzzle テストの題材 readonly class GuzzleSample public function __construct(
private ClientInterface $client, private ClientPoolFactoryInterface $poolFactory, private FulfilledHandlerInterface $fulfilledHandler, private RejectedHandlerInterface $rejectedHandler, private array $params, private int $concurrency = 10 ) {} • ClientInterface … GuzzleHttp\Clientの Interface(こっちの名前空間にしたいが割愛) • ClientPoolFactoryInterface … Pool作成ラッパーク ラスのInterface
2. Guzzleと仲良くなる Guzzle テストの題材 use GuzzleHttp\ClientInterface; use GuzzleHttp\Pool; class ClientPoolFactory
implements ClientPoolFactoryInterface { public function factory( ClientInterface $client, $requests, array $config = [] ): Pool { return new Pool($client, $requests, $config); } }
2. Guzzleと仲良くなる Guzzle テストの題材 readonly class GuzzleSample public function __construct(
private ClientInterface $client, private ClientPoolFactoryInterface $poolFactory, private FulfilledHandlerInterface $fulfilledHandler, private RejectedHandlerInterface $rejectedHandler, private array $params, private int $concurrency = 10 ) {}
2. Guzzleと仲良くなる Guzzle テストの題材 readonly class GuzzleSample public function __construct(
private ClientInterface $client, private ClientPoolFactoryInterface $poolFactory, private FulfilledHandlerInterface $fulfilledHandler, private RejectedHandlerInterface $rejectedHandler, private array $params, private int $concurrency = 10 ) {} 処理成功時と失敗時のハンドラー
2. Guzzleと仲良くなる Guzzle テストの題材 <?php namespace App\Sample; use GuzzleHttp\Psr7\Response; class
FulfilledHandler implements FulfilledHandlerInterface { public function handle(Response $res, array $reqParams): void { // Do something. } }
2. Guzzleと仲良くなる Guzzle テストの題材 <?php namespace App\Sample; use Psr\Http\Client\ClientExceptionInterface; class
RejectedHandler implements RejectedHandlerInterface { public function handle(ClientExceptionInterface $e, array $reqParams): void { // Do something. } }
2. Guzzleと仲良くなる Guzzle テストの題材 public function call(): void { $requests
= function ($params) { $uri = 'http://localhost/sample/'; foreach ($params as $key => $param) { yield $key => fn() => $this->client->requestAsync('GET', $uri . $param['id']); } };
2. Guzzleと仲良くなる Guzzle テストの題材 $pool = $this->poolFactory->factory( $this->client, $requests($this->params), [
'concurrency' => $this->concurrency, 'fulfilled' => fn() => $this->fulfilledHandler->handle(), 'rejected' => fn() => $this->rejectedHandler->handle(), ] ); $promise = $pool->promise(); $promise->wait(); }
2. Guzzleと仲良くなる Guzzle テストの題材 $pool = $this->poolFactory->factory( $this->client, $requests($this->params), [
'concurrency' => $this->concurrency, 'fulfilled' => fn() => $this->fulfilledHandler->handle(), 'rejected' => fn() => $this->rejectedHandler->handle(), ] ); $promise = $pool->promise(); $promise->wait(); }
2. Guzzleと仲良くなる Guzzle テスト class GuzzleSampleTest extends TestCase { public
function testSample(): void { // モックハンドラーを作成 // ここで定義した順にレスポンスが返される $mock = new MockHandler([ // 第一引数でステータスコードを指定できる // 第二引数でヘッダーオプションを指定できる // 第三引数でレスポンスボディを指定できる new Response(200, [], '{"id": 1}'), // 例外をスローすることもできる new RequestException('Error', new Request('GET', 'test'), new Response(500, [], 'Internal Server Error')) ]);
2. Guzzleと仲良くなる Guzzle テスト // ハンドラーをクライアントに登録 $handlerStack = HandlerStack::create($mock); $client
= new Client(['handler' => $handlerStack]);
2. Guzzleと仲良くなる Guzzle テスト // ハンドラーをクライアントに登録 $handlerStack = HandlerStack::create($mock); $client
= new Client(['handler' => $handlerStack]); MockHandlerを Clientに登録するのが肝 (GuzzleはHandlerを使ってよしなに処 理をしている)
2. Guzzleと仲良くなる Guzzle テスト $params = [ 'foo' => ['id'
=> 1, 'age' => 20], 'baz' => ['id' => 2, 'age' => 50], // これはエラーレスポンスが返る ]; // FulfilledHandlerとRejectedHandlerは上で定義したレスポンスを受け取れるので // 振る舞いを検証できる (new GuzzleSample( $client, new ClientPoolFactory(), new FulfilledHandler(), new RejectedHandler(), $params ))->call();
2. Guzzleと仲良くなる Guzzle テスト $params = [ 'foo' => ['id'
=> 1, 'age' => 20], 'baz' => ['id' => 2, 'age' => 50], // これはエラーレスポンスが返る ]; // FulfilledHandlerとRejectedHandlerは上で定義したレスポンスを受け取れるので // 振る舞いを検証できる (new GuzzleSample( $client, new ClientPoolFactory(), new FulfilledHandler(), new RejectedHandler(), $params ))->call(); これだけで本物のHTTPリク エストを投げずに、ふるまい を確認することが可能
2. Guzzleと仲良くなる Guzzle テスト $params = [ 'foo' => ['id'
=> 1, 'age' => 20], 'baz' => ['id' => 2, 'age' => 50], // これはエラーレスポンスが返る ]; // FulfilledHandlerとRejectedHandlerは上で定義したレスポンスを受け取れるので // 振る舞いを検証できる (new GuzzleSample( $client, new ClientPoolFactory(), new FulfilledHandler(), new RejectedHandler(), $params ))->call(); 簡単ですね
2. Guzzleと仲良くなる Guzzle テスト $params = [ 'foo' => ['id'
=> 1, 'age' => 20], 'baz' => ['id' => 2, 'age' => 50], // これはエラーレスポンスが返る ]; // FulfilledHandlerとRejectedHandlerは上で定義したレスポンスを受け取れるので // 振る舞いを検証できる (new GuzzleSample( $client, new ClientPoolFactory(), new FulfilledHandler(), new RejectedHandler(), $params ))->call(); • リクエストクラスにきちんとパラ メータを渡せているかなどの確認は ひと工夫必要 • 本当は書きたかったが断念
3. まとめ 1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ
3. まとめ • 並行処理を学んでいたら、プロセス, スレッド, 並列, 非同期が出てきて頭がパンク😇 • 並行処理、同時に走っているわけではなくタスク を切り替えて実行しているだけだった
• プロセス, スレッドは処理の実行単位を指す • 並行/並列は実行状態を指す ◦ プロセス, スレッドを学ぶと必ず処理が早くなるとは 限らないことがわかる ◦ スレッドセーフ難しそう
3. まとめ • 非同期I/Oというものがあり、Guzzleは素のまま だと非同期リクエストが用いられる • curl_exec, curl_multi が使い分けられている •
GuzzleはHTTPリクエストをいい感じに扱いやす いようにしてくれたもの • テストで本物のHTTPリクエストを叩かないよう にするのも簡単
3. まとめ Guzzleと仲良くなれましたね?
• GuzzleHttpはどのように非同期およびPromiseを実装しているのか ◦ https://qiita.com/ming_hentech/items/2daa60a33ad4811fd1e6 ◦ Guzzleの内部実装を詳しく説明している • 並行・並列とマルチスレッド・マルチプロセスの関係を整理する ◦ https://qiita.com/yukiyamamuro/items/de06878d6772ee2a1e76
• とまれーっ うごけーっ ◦ https://qiita.com/tadsan/items/63b8d84193498b1c6191 • Guzzle Promiseを使った非同期処理によるAPIコールの高速化 ◦ https://speakerdeck.com/suzuki/guzzle-promisewoshi-tuta-fei-tong-qi-ch u-li-niyoruapikorufalsegao-su-hua 3. まとめ 参考
• PHPでEventLoopを書いて非同期処理を完全に理解する ◦ https://speakerdeck.com/hanhan1978/understand-async-from-sync • curl_multiでHTTP並行リクエストを行うサンプル ◦ https://qiita.com/Hiraku/items/1c67b51040246efb4254 • PHPにおけるI/O多重化とyield
◦ https://www.slideshare.net/slideshow/phpioyield-phpcon2014/40136098 • Re: WebサーバーアーキテクチャとPHP実行方式の理解から始める php-fpmとはなにか? ◦ https://zenn.dev/bs_kansai/articles/3706c12408160c • 並行処理と並列処理|Goでの並行処理を徹底解剖! ◦ https://zenn.dev/hsaki/books/golang-concurrency/viewer/term 3. まとめ 参考
3. まとめ ご清聴ありがとうございました