【入門編】PHP 8.x Fiber(ファイバー)のスケジューリング挙動と協調的マルチタスクにおけるメモリ空間の物理メカニズム – PHPコア・内部エンジンと高速化・並行処理の極意解析バイブル

こんにちは。普段からモダンなWebフレームワークを使いこなし、「PHPってなんだかんだで速いよね」と感じつつも、「実際のところ、裏側でどうやって動いているんだろう?」と、一歩踏み込んだエンジニアリングに興味を持つ頃合いではないでしょうか。

他の言語、例えばNode.jsの非同期I/OやGoのGoroutineに触れたことがある人なら、「PHPでもっとスマートな並行処理ができないものか」と考えたことがあるはずです。PHP 8.1で導入された Fiber(ファイバー) は、まさにその答えの一つです。

今回は、このFiberがPHPのZendエンジン内部でどのようにコールスタックを操り、スレッドを使わずに「中断」と「再開」を実現しているのか、その物理的なメモリ空間の挙動まで含めて、じっくりと紐解いていきましょう。ここを理解すると、PHPの裏側が驚くほど綺麗に見えるようになりますよ。

—

1. 従来のPHPと「協調的マルチタスク」のパラダイムシフト

まず、私たちが普段書いているPHPスクリプトが、OSのプロセスやスレッドからどう見えているかを整理しましょう。

通常、PHP-FPMなどの環境では、1つのリクエストに対して1つのプロセス(またはスレッド)が割り当てられます。コードは上から下へ同期的に実行され、データベースのクエリや外部APIのレスポンスを待つ間、そのプロセスはCPUを明け渡すものの、メモリ空間やコールスタックはそのまま占有し続けます。

もし、1つのリクエスト内で数千件もの外部APIを非同期に叩こうとした場合、従来のやり方ではプロセスを増やすか、複雑なイベントループを泥臭く回すしかありませんでした。

ここで登場するのが Fiber(ファイバー) です。Fiberは、OSスレッドを使わずに、ユーザースペースで実行コンテキストを切り替える「協調的マルチタスク(Cooperative Multitasking)」 を実現します。

スレッドとの決定的な違い

  • OSスレッド: カーネルがコンテキストスイッチを管理します。CPUのレジスタ退避やメモリマップの切り替えが発生するため、数千個も作るとコンテキストスイッチのオーバーヘッドでCPUが爆発します。
  • Fiber(グリーンスレッドの一種): PHPのZendエンジン(ユーザーランド)が管理します。OSはPHPプロセスが1つのスレッドで動いているとしか認識していません。プロセスやスレッドの切り替えコストがゼロでありながら、あたかも並行処理しているかのように振る舞えます。

—

2. 内部構造:FiberはZendエンジンの中でどう扱われているか?

PHPのソースコード(C言語)のレベルを覗いてみましょう。PHP 8以降、Zendエンジンには `zend_fiber` という内部構造体が定義されています。

PHPの関数やメソッドが実行されるとき、Zendエンジンは 実行スタック(Execution Stack) 上に `zend_execute_data` という構造体を積んでいきます。通常、関数Aから関数Bを呼ぶと、スタックの積層が深くなりますよね。

しかし、Fiberを使うと、この挙動が劇的に変わります。

1. Fiberの生成 (`new Fiber(…)`)
Fiberをインスタンス化すると、PHPのヒープメモリ上に専用のコールスタック(Fiberスタック)と、その実行状態を保持するコンテキストが確保されます。この時点ではまだコードは実行されません。
2. 開始 (`$fiber->start()`)
現在のメインの実行コンテキストから、Fiber内部のコンテキストへ処理の制御権が移ります。
3. 中断 (`Fiber::suspend()`)
Fiberの内部から `suspend()` が呼ばれると、Zendエンジンは現在の実行位置(命令ポインタ)とコールスタックの状態をFiberの構造体の中に退避(保存)させ、制御権を呼び出し元(メイン側)へ一瞬で戻します。
4. 再開 (`$fiber->resume()`)
呼び出し元が退避された状態を元に戻し、中断されたまさにその行から処理を再開させます。

言葉だけだと抽象的なので、実際にFiberを使ったコードを見てみましょう。

start(‘開発者’);
echo “2. メイン受け取り: {$valueFromFiber}\n”;

echo “4. メイン: Fiberを再度resumeします。\n”;
// 中断していた箇所から再開させ、値を渡す
$finalValue = $fiber->resume(‘メインからの返答データ’);
echo “6. メイン受け取り: {$finalValue}\n”;

// Fiberの生存確認
if ($fiber->isTerminated()) {
echo “7. メイン: Fiberは完全に終了しました。\n”;
}

このコードを実行すると、出力は綺麗に 0 から 7 の順番で交互に制御権が移り変わるのが分かります。

0. メイン: Fiberを開始します。
1. Fiber内: こんにちは、開発者さん。
2. メイン受け取り: Fiberからの最初のメッセージ
4. メイン: Fiberを再度resumeします。
3. Fiber内: 再開しました。外から受け取った値: ‘メインからの返答データ’
6. メイン受け取り: Fiberからの最後のメッセージ
5. Fiber内: 処理を完了します。
7. メイン: Fiberは完全に終了しました。

コールスタックが「コピーされ、行き来する」この物理的な仕組みこそが、Fiberの真骨頂です。

—

3. 非同期I/Oイベントループとの連携メカニズム

「なるほど、処理を中断・再開できるのは分かったけれど、これの何がそんなに凄いの?」と思われたかもしれません。

Fiber単体では、ただの「手動で行ったり来たりできるサブルーチン」に過ぎません。真価を発揮するのは、非同期I/O(Event Loop)と組み合わせたときです。

例えば、データベースへのクエリやHTTPリクエスト(cURLなど)を投げる際、従来のPHPではレスポンスが返ってくるまでCPUはブロッキング(待機)状態になり、他の処理ができませんでした。

しかし、イベントループ(RevoltやAmp、ReactPHPなどのライブラリ)とFiberを組み合わせると、次のような魔法のような処理が可能になります。

resume($data);
});
});
}

実際のフレームワークやライブラリの内部では、この `suspend` と `resume` の間にイベントループ(epoll や kqueue をベースにした仕組み)が挟まれます。

これにより、「重いI/O待ちの間に、別のFiber(例えば別のHTTPリクエストの処理)にCPUの実行権を譲る」 という、Node.jsやGoでおなじみの非同期プログラミングが、見通しの良い同期的コードの見た目のままPHPで書けるようになります。

—

4. アーキテクトが語る:Fiberを使う際の注意点とメモリの罠

ここまでFiberの美しさを語ってきましたが、実務の現場で設計を行うリードアーキテクトとして、一つ強力な警告をしておきます。

PHPの歴史において、Fiberは比較的新しい機能です。そのため、既存のサードパーティ製ライブラリの多くは 「カラー関数(Colored Functions)」 の問題を抱えています。

カラー関数の問題(Zendエグゼキューターの制約)

関数の実行中にブロッキングなI/O(従来の `file_get_contents()` や古い PDO の同期実行など)を呼んでしまうと、その瞬間にOSスレッドごと処理がブロックされ、同じプロセス内で動いている他のすべてのFiberが強制的に足止めを食います。

つまり、
1. コードベースのすべてが非同期対応(Fiber対応)しているか、
2. もしくは、完全なブロッキングコードとして割り切るか、

のどちらかを選ぶ必要があります。中途半端に一部だけFiberを導入しても、どこか一箇所で同期的な重い処理(例: 重い正規表現や同期DB接続)が走ると、イベントループ全体がフリーズしてしまいます。

また、メモリ空間の観点からも、FiberごとにコールスタックのメモリがPHPのヒープ上に確保されるため、何万個ものFiberを同時に生成・放置すると、メモリリークやメモリプレッシャーの原因になります。GoのGoroutineが軽量だからといって無限に作れるのとは異なり、PHPでは「同時に並行処理させる数」を適切にコントロール(プール)する設計が求められます。

—

まとめ

今回は、PHP 8.xのFiberが内部でどのようにコールスタックを退避・復帰させ、協調的マルチタスクを実現しているのかを低レイヤの視点から解説しました。

  • Fiberは、OSスレッドではなくPHPのユーザースペース(Zendエンジン)で実行コンテキストを切り替える。
  • `Fiber::suspend()` と `Fiber::resume()` により、コールスタックの状態を保持したまま処理の中断・再開が可能。
  • イベントループと組み合わせることで、同期的で見通しの良いコードのまま、非同期I/Oによる高スループットな処理を実現できる。

PHPは「ただのテンプレートエンジン崩れのスクリプト言語」ではありません。Zendエンジンの内部構造とメモリの挙動を深く理解すれば、モダンで洗練された非同期Webアプリケーションの基盤としても十分に牙を剥く、極めて強力な言語です。

ぜひ、日々の開発の裏側にある「オペコードとスタックの踊り」に思いを馳せながら、より堅牢で美しいシステムを設計してみてください。あなたのPHPライフが、さらに深遠で楽しいものになることを願っています。

タイトルとURLをコピーしました