PHP

PHPの非同期処理とは|Fibers・ReactPHP・AMPHP・Swooleの違いと実装方法

PHPは1リクエストを上から順に実行して完結させる同期処理が基本の言語で、外部APIの呼び出しやデータベースのクエリを待つ間はプロセスがブロックされる。この待ち時間が積み重なると、複数のI/Oを扱う処理は遅くなる。PHP 8.1で標準機能のFibersが入ってからは、この「待ち」を重ね合わせて並行に処理する非同期プログラミングが、拡張のコンパイルなしでも現実的になった。この記事では、PHPの非同期処理が必要になる場面、8.1のFibers、ReactPHP・AMPHP・Swooleといった選択肢の違いと使い分け、AMPHPでの実装、そして避けるべき場面までを具体例つきで整理する。

まとめ:PHPの非同期処理の選び方

PHPで非同期処理を実現する手段は、標準機能・ライブラリ・拡張の3層に整理できる。結論を先に置く。

  • 土台はFibers(PHP 8.1標準):コルーチンの低レベルプリミティブ。単体では非同期にならず、イベントループと組み合わせて初めて機能する。
  • 手軽さ重視ならAMPHP v3:Fibers+Revoltイベントループで動き、PECL拡張なしでcomposer requireだけで導入できる。同期的に見えるコードで並行I/Oを書ける。
  • 明示的な制御と実績ならReactPHP:Promiseベースのイベントループ。エコシステムが大きく、AMPHPとも相互運用できる。
  • 本番の常駐サーバならSwoole:C++拡張でHTTPサーバやコルーチンを内蔵する。導入にコンパイルが要る分、性能と機能は最も厚い。

逆に、単純なCRUDのWebアプリなら非同期化しないほうが保守は楽で、後続の重い処理はメッセージキューに逃がすほうが適する場面も多い。まずは自分のケースが「本当に非同期が要るか」から確認していく。

PHPで非同期処理が必要になる場面と前提

非同期処理は「速くする魔法」ではなく、I/O待ちが支配的な処理でだけ効く。PHPでどこに効くかを先に押さえる。

同期実行が基本のPHPで並行処理が要る場面

非同期処理が効果を出すのは、CPU計算ではなくI/O待ちがボトルネックのときに限られる。PHPでは次のような処理が該当する。

  • 複数の外部APIを呼んで結果をまとめる(1本ずつ順に待つと合計待ち時間が積み上がる)
  • 複数のデータベースやHTTPエンドポイントへ同時に問い合わせるバッチ処理
  • WebSocketやSSEのように接続を張り続けてイベントを捌く常駐サーバ

逆に、画像のリサイズや暗号化のようなCPUバウンドな処理は、非同期にしても1コア上で速くはならない。この切り分けが最初の判断軸になる。

非同期・並行・並列の違い

用語が混同されやすいので区別しておく。非同期(asynchronous)はI/O待ちの間に別の処理へ制御を移す仕組みで、並行(concurrency)は複数のタスクを切り替えながら進める考え方を指す。FibersやAMPHPが実現するのはこの並行I/Oで、実行自体は1コア上で行われる。一方の並列(parallelism)は複数コアで同時に計算を走らせることで、PHPではparallel拡張やプロセスのフォークが担う。多くのWeb処理は外部I/O待ちが支配的なので、まず効くのは並列より並行のほうだ。

PHPが非同期を苦手としてきた理由

PHPはもともと1リクエスト1プロセスで完結し、レスポンスを返したら状態を破棄するモデルで発展した。リクエストをまたいで生き続けるイベントループが標準に無く、file_get_contentsや標準のPDOといった主要なI/O関数がブロッキング(結果が返るまで処理を止める)だったため、言語レベルで非同期を書く土台が長らく無かった。この状況を変えたのが、次に述べるPHP 8.1のFibersだ。

PHP 8.1のFibers:非同期処理の土台

主力の検索需要が集まるFibersは、PHPの非同期処理を語るうえで避けて通れない中核機能だ。何を解決し、何を解決しないのかを正確に押さえる。

Fibersの正体:中断・再開できる軽量実行コンテキスト

Fibersは、PHP 8.1(2021年11月)で標準導入されたコルーチン(グリーンスレッド)の仕組みで、任意の地点で実行を一時停止し、あとから再開できる軽量な実行コンテキストだ。各Fiberは独自のスタックを持ち(サイズはfiber.stack_sizeで調整できる)、ネストした関数呼び出しの深い位置でも一時停止できる。同じ「中断できる関数」であるジェネレータが戻り値の型を変えてしまい浅い位置でしか止められないのに対し、Fibersは呼び出し側のコードに影響を与えず全スタックを保持したまま中断できる点が本質的な違いだ。

Fibersの基本コード

Fiberはnew Fiber()で生成し、start()で開始、内部のFiber::suspend()で中断、外側のresume()で再開する。中断時に値を受け渡せる。

<?php

$fiber = new Fiber(function (): void {
    echo "処理を開始\n";
    $received = Fiber::suspend("中断中の値");
    echo "再開: {$received}\n";
});

$value = $fiber->start();   // "中断中の値" が返る
echo "外側: {$value}\n";
$fiber->resume("再開の値"); // Fiber 内の $received に渡る

このように、Fiberは「止めて、値を渡して、また続きから動かす」制御を提供する。ただしこれ自体はスケジューラではない点に注意が要る。

Fibers単体の限界:スケジューラを含まない

ここが誤解されやすい。Fibersが提供するのは中断・再開のプリミティブだけで、「どのFiberをいつ再開するか」を管理するイベントループは含まれない。非ブロッキングI/Oを待つ間に別のFiberへ制御を渡し、I/Oが完了したら元へ戻す——この調停役が無ければ、Fibersを並べても並行にはならない。だから実務では、Fibersの上にイベントループを載せたライブラリ(AMPHPやReactPHP)を使うのが現実的な選択になる。Fibersはあくまで、それらのライブラリが同期的に見えるコードを実現するための土台だ。

非同期ライブラリ・拡張の選択肢と違い

Fibersを土台に、実際の非同期処理は複数のライブラリ・拡張が担う。それぞれの立ち位置と、選ぶ基準を整理する。

手段 実行基盤 導入方法 主な用途
Fibers PHP 8.1標準 不要(言語機能) ライブラリの土台
AMPHP v3 Fibers+Revolt composerのみ 並行I/O・スクリプト
ReactPHP Promise+イベントループ composerのみ 常駐サーバ・イベント駆動
Swoole C++拡張 PECL(要コンパイル) 本番HTTPサーバ・高負荷

AMPHP v3:拡張不要でFibersを活かす

AMPHPは、Fibers(8.1以降)とRevoltイベントループの上に構築された非同期ライブラリ群だ。v2まではジェネレータとyieldでコルーチンを表現していたが、v3ではFibersを直接使う設計に変わり、Promiseのthenチェーンを書かずに同期的に見えるコードで並行処理を書ける。最大の利点は、PECL拡張のコンパイルが要らずcomposer requireだけで動くことで、多くのチームにとって導入コストが最も低い。土台のRevoltは、AMPHPとReactPHPが共同で運用する共有イベントループになっている。

ReactPHP:Promiseベースの明示的な制御

ReactPHPは、イベントループとPromiseを中心に据えた老舗のライブラリで、非同期の各段階を明示的に組み立てたい場合に向く。ソケット、HTTP、DNS、子プロセスなど周辺コンポーネントのエコシステムが大きく、実績も豊富だ。AMPHP v3とはRevoltを介して相互運用でき(revolt/event-loop-adapter-react)、どちらかに完全にロックインされるわけではない。Promiseの流儀に馴染んでいるチームや、既存のReactPHP資産がある場合の第一候補になる。

Swoole/OpenSwoole:本番サーバ向けのC++拡張

Swoole(およびフォークのOpenSwoole)は、ライブラリではなくC/C++で書かれたPHP拡張で、コルーチン・非同期I/O・本番運用に耐えるHTTPサーバやワーカープロセスをPHPランタイム自体に組み込む。AMPHPやReactPHPがユーザーランドのPHPコードで非同期を実装するのに対し、Swooleはランタイムレベルで機能を足すため性能面の上限は高い。半面、PECLでのコンパイルとデプロイが必要になり、環境構築の難度は上がる。実測ベンチ(2026年のAccestoの計測)では、DBクエリとHTTP呼び出しを並行実行させたケースでAMPHPもSwooleもおよそ260msと同等で、性能差より導入コストで選ぶ判断が現実的だ。

軽量な並行化:curl_multi・parallel・pcntl_fork

フレームワークを入れずに済ませたい限定的な用途では、標準機能で足りることもある。複数のHTTPリクエストを同時に投げるだけならcurl_multi系の関数で並行取得でき、Guzzleの非同期リクエストも内部でこれを使う。CPUバウンドな処理を複数コアで並列化したいならparallel拡張(真のスレッド)、CLIで重い処理を子プロセスに分けたいならpcntl_forkが選択肢になる。いずれも汎用の非同期基盤ではなく、目的が限定されるときの軽量な代替と位置づけるのが正しい。

AMPHPで非同期処理を実装する

最も導入しやすいAMPHP v3を例に、実際のコードで並行I/Oを書いてみる。ここでは環境構築の詳細ではなく、非同期処理そのものの書き方に集中する。

インストールと最小構成

AMPHPはPHP 8.1以上とComposerがあれば動く。拡張のインストールは不要だ。用途に応じて必要なパッケージを追加する。

# コア(イベントループとFiberベースの基盤)
composer require amphp/amp

# 非同期HTTPクライアントを使う場合
composer require amphp/http-client

Revoltイベントループはamphp/ampの依存として入るため、個別に指定する必要はない。

複数のHTTPリクエストを並行取得する

非同期の効果が最も分かりやすいのが、複数の外部リクエストの並行実行だ。Amp\async()で各処理をFiberとして起動し、Amp\Future\await()ですべての完了を待つ。順に待てば所要時間は各リクエストの合計だが、並行なら最も遅い1本の時間で済む。

<?php

require __DIR__ . '/vendor/autoload.php';

use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use function Amp\async;
use function Amp\Future\await;

$client = HttpClientBuilder::buildDefault();

$urls = [
    'https://example.com/api/a',
    'https://example.com/api/b',
    'https://example.com/api/c',
];

$futures = [];
foreach ($urls as $url) {
    $futures[] = async(function () use ($client, $url) {
        $response = $client->request(new Request($url));
        return $response->getBody()->buffer();
    });
}

// 3本を並行に実行し、すべての結果をまとめて受け取る
$results = await($futures);

async()の中はthenチェーンではなく同期的な見た目のまま書けている。これがFibersを土台にしたv3の書き味だ。

PHP 8.1のFibersとAMPHP v3の関係

上のコードでFiberクラスを直接触っていないのは、AMPHPが内部でFibersを使い、async()await()という高水準のAPIに包んでいるからだ。先述のとおりv3はFibersを直接使う設計に切り替わっており(v2のyieldベースから変わった点は前節の比較で触れた)、戻り値が特殊なジェネレータ型になることもなく、通常の関数と同じ感覚で非同期処理を組める。つまり開発者はFibersの低レベルAPIを意識する必要がなく、その恩恵だけをライブラリ経由で受け取れる構造になっている。

非同期処理を使うべき場面・避けるべき場面

非同期化は保守コストと引き換えの選択だ。何でも非同期にするとむしろ複雑さが増す。導入する/しないの判断基準を具体的な条件で示す。

使うべき場面は、外部I/O待ちが処理時間の大半を占めるケースに絞られる。多数の外部APIやDBへ同時に問い合わせるバッチ、WebSocket・SSEのように接続を張り続けてイベントを捌く常駐サーバ、大量のHTTP取得を束ねるクローラなどが典型で、ここでは並行化が待ち時間を実測で数分の一に縮める。

避けるべき場面もはっきりしている。フォームとCRUDが中心の一般的なWebアプリは、PHP-FPMの1リクエスト1プロセスモデルで十分に速く、非同期化は複雑さに見合わない。メール送信やレポート生成のような「後回しでよい重い処理」は、アプリを非同期化するより、SQS・Lambda・EventBridgeを用いた非同期処理の実践と運用方法で解説しているようなメッセージキューへ逃がすほうが運用は安定する。加えて、チームがイベントループの挙動やFiberの中断・再開に習熟していない段階での本番投入は、デバッグ困難な不具合を招きやすい。非同期は「I/O待ちが支配的」かつ「チームが仕組みを理解している」ときの道具だと割り切るのが安全だ。

よくある質問

PHPで非同期処理は本当に必要ですか?

多くのWebアプリでは必須ではない。PHP-FPMの同期モデルで十分なケースが大半で、非同期が効くのは複数の外部I/Oを同時に待つ処理や常駐サーバに限られる。まず自分のボトルネックがI/O待ちかを確認してから検討するのが順序だ。

FibersとReactPHP・AMPHPはどう使い分けますか?

Fibersは低レベルのプリミティブで、通常はこれを直接書かない。実務ではFibersを土台にしたAMPHP(拡張不要・同期的な書き味)かReactPHP(Promiseベース・明示的制御)を使い、Fibers単体はライブラリを作る側が扱うと考えてよい。

SwooleとAMPHPの違いは何ですか?

SwooleはC++で書かれたPHP拡張で、コンパイルとデプロイが必要な代わりに本番HTTPサーバやコルーチンをランタイムに組み込む。AMPHPはユーザーランドのPHPライブラリでcomposer requireだけで導入できる。性能は近く、導入の手軽さを取るならAMPHP、ランタイムごと非同期化して高負荷を捌くならSwooleが向く。

非同期処理と並列処理の違いは何ですか?

非同期(並行)はI/O待ちの間に別のタスクへ制御を移す仕組みで、1コア上で動く。並列はマルチコアで計算を同時に走らせることで、PHPではparallel拡張やプロセスのフォークが担う。Web処理の多くはI/O待ちが支配的なので、まず効くのは並行のほうだ。

PHPの非同期処理はどのバージョンから使えますか?

言語標準のFibersはPHP 8.1(2021年11月)から使える。最新のPHP 8.5でも標準機能として利用でき、AMPHP v3やReactPHPもFibersを前提にした構成になっている。8.1未満ではジェネレータやライブラリ独自の仕組みに頼る必要があった。

関連記事

資料請求

RELATED POSTS 関連記事