Slide 1

Slide 1 text

並行処理を学び Guzzleと仲良くなる 2024/06/22 ペチコン福岡2024 by しまぶ@shimabox 


Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

はじめに ● これからお話しする並行/並列処理に関することは、これらの本を(少し)読 んで得た自分なりの理解に基づいています Go言語 100Tips ありがちなミスを把握し、実装を最適化する https://www.amazon.co.jp/gp/product/4295017531 並行プログラミング入門 ―Rust、C、アセンブリによる実装か らのアプローチ https://www.amazon.co.jp/dp/4873119596

Slide 4

Slide 4 text

1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ アジェンダ

Slide 5

Slide 5 text

1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ アジェンダ

Slide 6

Slide 6 text

1. 並行処理を学ぶ ● プロセス / スレッド ● 並行処理 / 並列処理 ● 非同期処理 並行処理を学ぶその前にもろもろ整理

Slide 7

Slide 7 text

1. 並行処理を学ぶ ● プロセスはプログラムの実行の単位 ● 各プロセスは独立したメモリ空間を持ち、他のプ ロセスとはメモリを共有しない ● 各プロセスは独自のリソース(ファイルハンド ル、データ、コード)を持つ プロセス とは

Slide 8

Slide 8 text

1. 並行処理を学ぶ ● 複数のプロセス → マルチプロセス ● ひとつのプロセス → シングルプロセス ● 生成には比較的大きなオーバーヘッドが伴う プロセス とは

Slide 9

Slide 9 text

1. 並行処理を学ぶ プロセス とは ● 大きく分けて以下の4つの状態を取る ● 実行前状態 ● 実行状態 ● 待機状態 ● 終了状態

Slide 10

Slide 10 text

1. 並行処理を学ぶ プロセス とは P2 図1-1 プロセスの状態遷移図

Slide 11

Slide 11 text

1. 並行処理を学ぶ プロセス とは 待機状態へ遷移する理由 ● データの到着を待つため ○ 処理対象が無ければ無意味 ● リソースの空きを待つため ○ 計算できる道具が利用され ていたら待つ ● 自発的に待機状態となるため ○ タイマー処理とか ○ リソースを専有しない

Slide 12

Slide 12 text

1. 並行処理を学ぶ プロセス とは ● プロセスは実行状態と待 機状態への遷移を繰り返 しながら処理を進めてい く

Slide 13

Slide 13 text

1. 並行処理を学ぶ プロセス とは P3 図1-2 あるプロセスの状態遷移と計算途中状態より抜粋

Slide 14

Slide 14 text

1. 並行処理を学ぶ ● プロセス内で実行される軽量な実行単位 ● スレッドはプロセス内のリソースを共有 ○ 例:ファイルハンドル、データ ○ メモリ空間を共有する ○ データの競合が発生する可能性がある ● 生成のオーバーヘッドはプロセスに比べて小さい ● スレッド間のコンテキストスイッチも比較的高速 スレッド とは

Slide 15

Slide 15 text

1. 並行処理を学ぶ ● アプリケーションを起動すると1つ(あるいは少 数)のプロセスが生成されて、プロセス内で複数 のスレッドが生成される → マルチスレッド ● 1つのプロセスが1つのスレッドしか持たない → シングルスレッド ● プログラム内で実行される処理は`タスク`と呼ん だりする(プロセス、スレッドひっくるめて) スレッド とは

Slide 16

Slide 16 text

1. 並行処理を学ぶ プロセス / スレッド P4 図1-4 OSプロセスとスレッド

Slide 17

Slide 17 text

1. 並行処理を学ぶ プロセス / スレッド プロセスやスレッドによって実行される個々の処理 の集まりを `タスク` と呼んだりする

Slide 18

Slide 18 text

1. 並行処理を学ぶ プロセス / スレッド ● プロセスは別プロセスと会話できない ○ (基本的には。IPCをググれ。) ● スレッドは同じプロセス内で会話できる ❌(基本的に) ❌ ◯

Slide 19

Slide 19 text

1. 並行処理を学ぶ ● 一つのCPU,コアが複数のタスクを短い時間ごと に切り替えながら実行する ○ プロセスを切り替えたり ○ マルチスレッドを使い高速に切り替えたり ● タスクが「同時に」進行しているように見えるが 実際には交互に実行されている ● I/Oバウンドなタスクに有効 ○ I/O操作の待機中に他のタスクを進行させることで、 待ち時間を有効に活用する 並行処理(Concurrency) とは

Slide 20

Slide 20 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは P3 図1-2 あるプロセスの状態遷移と計算途中状態

Slide 21

Slide 21 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考

Slide 22

Slide 22 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 同じコアで 動いている プロセス

Slide 23

Slide 23 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 ここを高速に 切り替えている

Slide 24

Slide 24 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 を参考 同じプロセス内 で動いている スレッド

Slide 25

Slide 25 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド

Slide 26

Slide 26 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド ● プロセスは分かれてい るから安定 ● プロセスの生成、コンテ キストスイッチのオー バーヘッドのコストが大 きい ● コンテキストスイッチの コストは小さい ● スレッドセーフを考える 必要がある ● プロセスでも共有リソー スを扱う場合注意は必 要(ファイルとか)

Slide 27

Slide 27 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド ● マルチプロセス x マルチスレッド というのももちろんある ● コンテキストスイッチの コストは小さい ● スレッドセーフを考える 必要がある ● プロセスでも共有リソー スを扱う場合注意は必 要(ファイルとか)

Slide 28

Slide 28 text

1. 並行処理を学ぶ 並行処理(Concurrency) とは マルチプロセス マルチスレッド ● マルチプロセス x マルチスレッド というのももちろんある ● マルチスレッドプログラ ミングとか恐怖でしかな いな

Slide 29

Slide 29 text

1. 並行処理を学ぶ ● 複数のCPUまたはコア(マルチコア)がそれぞれ異 なるタスクを同時に実行する ○ コアが一つしかない場合(シングルコア)、 真の並列処理はできない ● 物理的に同時に実行される ● CPUバウンドなタスク(計算/画像処理)に適して いる ○ CPUリソースを最大限に活用 並列処理(Parallelism) とは

Slide 30

Slide 30 text

1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-3 あるプロセスAとBの実行状態と並行性

Slide 31

Slide 31 text

1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-3 あるプロセスAとBの実行状態と並行性 違うコアで 動いている タスク

Slide 32

Slide 32 text

1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-5 あるプロセスAとBの実行状態と並列性

Slide 33

Slide 33 text

1. 並行処理を学ぶ 並列処理(Parallelism) とは P4 図1-5 あるプロセスAとBの実行状態と並列性 実行状態が被っ ているところ 違うコアで 動いている タスク

Slide 34

Slide 34 text

1. 並行処理を学ぶ 並行処理 / 並列処理 ● 並行処理は並列処理を包 含する ○ 並行処理が並列処理を可能 にする ● “並行処理とは、一度に多 くを扱うことです。 並列処理とは、一度に多 くを行うことです。” - Rob Pike

Slide 35

Slide 35 text

1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ここにコンビニがあるじゃろ

Slide 36

Slide 36 text

1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ここにコンビニがあるじゃろ CPU

Slide 37

Slide 37 text

1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える そこにはよくある風景があるじゃろ

Slide 38

Slide 38 text

1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える そこにはよくある風景があるじゃろ 待ち プロセス スレッド

Slide 39

Slide 39 text

プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える ひとつひとつ捌いていたら時間がかかる スレッド

Slide 40

Slide 40 text

プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 逐次処理 スレッド

Slide 41

Slide 41 text

プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 処理するもの(スレッド)を増やす スレッド

Slide 42

Slide 42 text

プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 店員さんがレジや電子レンジを駆使して処理をする スレッド

Slide 43

Slide 43 text

プロセス 待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 並行 スレッド

Slide 44

Slide 44 text

待ち 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える これも並行

Slide 45

Slide 45 text

CPU 1. 並行処理を学ぶ 並行処理 / 並列処理 コンビニで例える 並列 CPU

Slide 46

Slide 46 text

1. 並行処理を学ぶ ● 注意したいこと ○ プロセス/スレッド/並行/並列 といろいろ出て きたが、それぞれが独立した単語 ○ プロセス/スレッドは実行単位を指す ■ マルチ/シングルがさらにある ○ 並行/並列は実行状態を指す ○ 並列はマルチコアのみ 並行処理 / 並列処理

Slide 47

Slide 47 text

1. 並行処理を学ぶ ● 注意したいこと ○ プロセス/スレッド/並行/並列 といろいろ出て きたが、それぞれが独立した単語 ○ プロセス/スレッドは実行単位を指す ■ マルチ/シングルがさらにある ○ 並行/並列は実行状態を指す ○ 並列はマルチコアのみ 並行処理 / 並列処理 混ぜるな危険 😇

Slide 48

Slide 48 text

1. 並行処理を学ぶ ● 注意したいこと ○ 並行/並列にしたから といって必ず早くなる とは限らない ○ オーバーヘッドはある ○ アムダールの法則とい うのもあってだな 並行処理 / 並列処理 P8 図1-6 並列化による高速化の例

Slide 49

Slide 49 text

1. 並行処理を学ぶ ● シングルスレッドでしか動かないものは並行処理 できないの? シングルスレッドは?

Slide 50

Slide 50 text

1. 並行処理を学ぶ ● シングルスレッドでしか動かないものは並行処理 できないの? ● キューを使う ○ ワーカーを立ち上げて別プロセスで処理をさせる シングルスレッドは?

Slide 51

Slide 51 text

1. 並行処理を学ぶ ● シングルスレッドでしか動かないものは並行処理 できないの? ● キューを使う ○ ワーカーを立ち上げて別プロセスで処理をさせる ● 非同期I/Oというのもある シングルスレッドは?

Slide 52

Slide 52 text

1. 並行処理を学ぶ ● ファイル操作、ネットワーク通信(HTTPリクエス ト/ソケット)、データベースアクセスなど、すべ てのI/O操作を非同期に実行する ● たとえば、JS,Node.js はシングルスレッドで動 作するが、イベントループという仕組みを使って 非同期I/Oを実現している 非同期I/O とは?

Slide 53

Slide 53 text

1. 並行処理を学ぶ ● 非同期I/Oは、I/O操作を開始しその完了を待つ間 に他の処理を進める仕組み ● イベントループがI/Oの完了を監視し、完了時に コールバック関数を実行する ● これにより待ち時間が最小化される ● C10K問題もうんたらかんたら 非同期I/O とは?

Slide 54

Slide 54 text

1. 並行処理を学ぶ イベントループ とは? ● 非同期タスクの登録 ○ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される ● タスクキュー ○ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ○ たとえばPromise(成功か失敗のどちらかを保持している) ● イベントループの実行 ○ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ○ ループしながらイベントを監視しているイメージ

Slide 55

Slide 55 text

1. 並行処理を学ぶ イベントループ とは? ● 非同期タスクの登録 ○ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される ● タスクキュー ○ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ○ たとえばPromise(成功か失敗のどちらかを保持している) ● イベントループの実行 ○ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ○ イベントをループしながら監視しているイメージ 同期的なプログラミング言語の視点から非同期処理を理解する https://speakerdeck.com/hanhan1978/understand-async-from-sync

Slide 56

Slide 56 text

1. 並行処理を学ぶ イベントループ とは? ● 非同期タスクの登録 ○ 非同期操作が発生すると、その操作はイベントループによって管 理されるキュー(待機リスト)に登録される ● タスクキュー ○ 非同期操作が完了した後、その結果(コールバック関数)はタスク キュー(待機中のタスクリスト)に追加される ○ たとえばPromise(成功か失敗のどちらかを保持している) ● イベントループの実行 ○ イベントループは、タスクキュー内のタスクがある限り、これら のタスクを一つずつ取り出し、実行する(デキュー) ○ イベントをループしながら監視しているイメージ 同期的なプログラミング言語の視点から非同期処理を理解する https://speakerdeck.com/hanhan1978/understand-async-from-sync 神(所)

Slide 57

Slide 57 text

1. 並行処理を学ぶ 並行処理(その他もろもろ) 完全に理解しましたね?

Slide 58

Slide 58 text

2. Guzzleと仲良くなる 1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ

Slide 59

Slide 59 text

Guzzleと仲良くなっていきたいの ですが 2. Guzzleと仲良くなる

Slide 60

Slide 60 text

ここまで聞いて 何かソワソワしている人が いるかもしれませんね 2. Guzzleと仲良くなる

Slide 61

Slide 61 text

PHP 2. Guzzleと仲良くなる

Slide 62

Slide 62 text

そう、PHPは シングルスレッドモデルです 2. Guzzleと仲良くなる

Slide 63

Slide 63 text

先に説明したような並行/並列処理 は直接サポートしていない (ライブラリや拡張モジュールが別途必要) 2. Guzzleと仲良くなる

Slide 64

Slide 64 text

Guzzleでググると 並行処理だとか並列処理だとか 聞くがどうやっているのか 2. Guzzleと仲良くなる

Slide 65

Slide 65 text

2. Guzzleと仲良くなる PHPのHTTPクライアントでHTTPリクエストを簡単に送信でき、以下 の特徴があります。 ● 同じインターフェースで同期・非同期リクエストの送信が可能 ● PSR-7およびPSR-18をサポートしている ● 基本的なHTTPトランスポートを抽象化(cURL、PHPストリーム、 ソケット、非ブロッキングイベントループなどへの依存なし) ● ミドルウェアシステムにより、クライアントの動作をカスタマイズ 可能 これらの機能により、複雑なHTTP操作を簡単に実行できます。 Guzzle https://github.com/guzzle/guzzle

Slide 66

Slide 66 text

2. Guzzleと仲良くなる PHPのHTTPクライアントでHTTPリクエストを簡単に送信でき、以下 の特徴があります。 ● 同じインターフェースで同期・非同期リクエストの送信が可能 ● PSR-7およびPSR-18をサポートしている ● 基本的なHTTPトランスポートを抽象化(cURL、PHPストリーム、 ソケット、非ブロッキングイベントループなどへの依存なし) ● ミドルウェアシステムにより、クライアントの動作をカスタマイズ 可能 これらの機能により、複雑なHTTP操作を簡単に実行できます。 Guzzle https://github.com/guzzle/guzzle 一言も並行処理だとか謳っていない 😇

Slide 67

Slide 67 text

2. Guzzleと仲良くなる ● 同じインターフェースで同期・非同期リクエスト の送信が可能 Guzzleは非同期リクエストをサポートしており、こ れにより並行的に複数のHTTPリクエストを処理す ることが可能。 Guzzle https://github.com/guzzle/guzzle

Slide 68

Slide 68 text

2. Guzzleと仲良くなる ● 非同期I/Oと似ているが、 特にネットワーク通信 (特にHTTPリクエスト) に焦点を当てた非同期処 理 ● リクエストを非同期に送 信し、プロミスで管理し て、完了時にコールバッ クで処理 非同期リクエスト とは

Slide 69

Slide 69 text

2. Guzzleと仲良くなる ● 素?のGuzzleは並行処理も非同期I/Oもサポート していない ○ イベントループの実装もない ● 並行、非同期I/Oを行うには拡張モジュールやラ イブラリが別途必要 Guzzle https://github.com/guzzle/guzzle

Slide 70

Slide 70 text

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/

Slide 71

Slide 71 text

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 おとなしくそれを使う

Slide 72

Slide 72 text

申し訳ないですが、 素のGuzzleと仲良くなります 2. Guzzleと仲良くなる

Slide 73

Slide 73 text

2. Guzzleと仲良くなる Guzzle https://github.com/guzzle/guzzle ● Composerでサクッといれられる(7系入れてこ?) ○ composer require guzzlehttp/guzzle

Slide 74

Slide 74 text

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 ~

Slide 75

Slide 75 text

2. Guzzleと仲良くなる Guzzle サンプルその1(同期リクエスト) sendRequest(new Request('GET', 'http://localhost/sample/1')); echo $res->getBody();

Slide 76

Slide 76 text

2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) json([ 'id' => $id, ]); }); sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL;

Slide 77

Slide 77 text

2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) json([ 'id' => $id, ]); }); sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; $ php sample2.php 3.629311876 秒

Slide 78

Slide 78 text

2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) json([ 'id' => $id, ]); }); sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; 順序に依存関係のない処理であれば、 もったいない🤔

Slide 79

Slide 79 text

2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) json([ 'id' => $id, ]); }); sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; 順序に依存関係のない処理であれば、 もったいない🤔

Slide 80

Slide 80 text

2. Guzzleと仲良くなる Guzzle サンプルその2(同期リクエスト) json([ 'id' => $id, ]); }); sendRequest('略'); $client->sendRequest('略'); $client->sendRequest('略'); $total = (hrtime(true) - $start) / 1e+9; echo "{$total} 秒" . PHP_EOL; イメージとしてはこうしたい

Slide 81

Slide 81 text

2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) $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; } }

Slide 82

Slide 82 text

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が解決されているので後は好きにしてもろうて

Slide 83

Slide 83 text

2. Guzzleと仲良くなる Guzzle サンプルその3(非同期リクエスト) $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 秒 イメージはあってそう

Slide 84

Slide 84 text

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();

Slide 85

Slide 85 text

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が解決される

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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(); }

Slide 89

Slide 89 text

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(); }

Slide 90

Slide 90 text

● 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(); }

Slide 91

Slide 91 text

● 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(); }

Slide 92

Slide 92 text

● 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を使ってよしなに 処理をしている

Slide 93

Slide 93 text

● 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リクエストをいい感じ に扱いやすいようにラップ してくれているんだね

Slide 94

Slide 94 text

2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) ● 非同期にリクエストを投げられるのは分かった ● ただ、もう少し柔軟にリクエストを投げたい ● 実行数がよめない、大量に実行したいケースとか ○ 一気にドーンではなく(メモリも気になるし) ● たとえば100個のリクエストを同時に投げるので はなく、10個ずつ10回に分けて実行したい場合 ● そこで、Poolを使う

Slide 95

Slide 95 text

2. Guzzleと仲良くなる Guzzle サンプルその4(非同期リクエスト Pool) $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();

Slide 96

Slide 96 text

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); } };

Slide 97

Slide 97 text

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();

Slide 98

Slide 98 text

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(); レスポンスはリクエストを 投げた順番に返ってこない

Slide 99

Slide 99 text

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) => [], ]);

Slide 100

Slide 100 text

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 を活用すると捗る

Slide 101

Slide 101 text

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) => [], ]);

Slide 102

Slide 102 text

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 ) {}

Slide 103

Slide 103 text

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 ) {} 差し替えられるように

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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); } }

Slide 106

Slide 106 text

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 ) {}

Slide 107

Slide 107 text

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 ) {} 処理成功時と失敗時のハンドラー

Slide 108

Slide 108 text

2. Guzzleと仲良くなる Guzzle テストの題材

Slide 109

Slide 109 text

2. Guzzleと仲良くなる Guzzle テストの題材

Slide 110

Slide 110 text

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']); } };

Slide 111

Slide 111 text

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(); }

Slide 112

Slide 112 text

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(); }

Slide 113

Slide 113 text

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')) ]);

Slide 114

Slide 114 text

2. Guzzleと仲良くなる Guzzle テスト // ハンドラーをクライアントに登録 $handlerStack = HandlerStack::create($mock); $client = new Client(['handler' => $handlerStack]);

Slide 115

Slide 115 text

2. Guzzleと仲良くなる Guzzle テスト // ハンドラーをクライアントに登録 $handlerStack = HandlerStack::create($mock); $client = new Client(['handler' => $handlerStack]); MockHandlerを Clientに登録するのが肝 (GuzzleはHandlerを使ってよしなに処 理をしている)

Slide 116

Slide 116 text

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();

Slide 117

Slide 117 text

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リク エストを投げずに、ふるまい を確認することが可能

Slide 118

Slide 118 text

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(); 簡単ですね

Slide 119

Slide 119 text

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(); ● リクエストクラスにきちんとパラ メータを渡せているかなどの確認は ひと工夫必要 ● 本当は書きたかったが断念

Slide 120

Slide 120 text

3. まとめ 1. 並行処理を学ぶ 2. Guzzleと仲良くなる 3. まとめ

Slide 121

Slide 121 text

3. まとめ ● 並行処理を学んでいたら、プロセス, スレッド, 並列, 非同期が出てきて頭がパンク😇 ● 並行処理、同時に走っているわけではなくタスク を切り替えて実行しているだけだった ● プロセス, スレッドは処理の実行単位を指す ● 並行/並列は実行状態を指す ○ プロセス, スレッドを学ぶと必ず処理が早くなるとは 限らないことがわかる ○ スレッドセーフ難しそう

Slide 122

Slide 122 text

3. まとめ ● 非同期I/Oというものがあり、Guzzleは素のまま だと非同期リクエストが用いられる ● curl_exec, curl_multi が使い分けられている ● GuzzleはHTTPリクエストをいい感じに扱いやす いようにしてくれたもの ● テストで本物のHTTPリクエストを叩かないよう にするのも簡単

Slide 123

Slide 123 text

3. まとめ Guzzleと仲良くなれましたね?

Slide 124

Slide 124 text

● 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. まとめ 参考

Slide 125

Slide 125 text

● 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. まとめ 参考

Slide 126

Slide 126 text

3. まとめ ご清聴ありがとうございました