こんにちは。JavaやScala、Node.jsといった他の高水準言語で非同期処理や並行プログラミングの洗礼を浴び、モダンなPHP(8.1以降)にやってきたあなたなら、きっと一度はこう思ったことがあるはずです。
「PHPでも、あのエレガントな並行処理モデルは使えないのだろうか?」と。
シェアード・ナッシング(共有なし)のRequest-Responseライフサイクルを基本としてきたPHPですが、Swoole、ReactPHP、そしてPHP 8.1で遂にコアに搭載された Fiber(ファイバー) の登場により、私たちの手元には「非同期・協調的マルチタスク」という強力な武器が与えられました。
しかし、ここでエンジニアとしての嗅覚が鋭いあなたなら、さらに一歩踏み込んだ疑問に突き当たるはずです。
――「で、このFiberは、ScalaのAkkaなどが採用する『アクターモデル』と何が違うのか? エンタープライズの荒波にも耐える設計をするなら、どちらを選ぶべきなのか?」と。
今回は、PHPの内部実行エンジン(Zend VM)の挙動やメモリ管理の視点も交えながら、この「Fiber vs アクターモデル」の深淵へとあなたを導きましょう。ここを理解すれば、PHPの裏側がいかに美しく、そしてどう設計すべきかが手に取るように見えてきますよ。
—
1. そもそもFiberとは何か?Zend VMのスタック管理から紐解く本質
他の言語から来た人が最初に勘違いしやすいのですが、PHPのFiberは「スレッド」ではありません。OSのスレッドでもなければ、グリーン・スレッド(完全自動スケジューリングされる軽量スレッド)とも違います。
Fiberの本質は、「スタックを持つ独立した実行コンテキスト(コールスタック)の切り替え機構」です。これを、プログラマが明示的に制御する(協調的:Cooperative)仕組みです。
Zend VMの裏側で何が起きているか?
通常のPHP関数呼び出しは、Zend VMのコールスタック(`execute_data` の連結リスト)上でリニアにプッシュ・ポップされます。
しかし、Fiberが生成されると、PHPのコア(Zend Engine)はそのFiber専用の独立したヒープ領域とスタック領域のコンテキストを切り出します。
[通常のリクエスト処理]
Zend VM Stack: [Main] -> [Controller] -> [Service] (ここでI/Oブロック) -> 停止
[Fiberによる協調的処理]
Fiber A (Context): [Task A processing…] ──(suspend)──>
| (Control returns to Loop)
Fiber B (Context): <──(resume)── [Task B processing...] <──┘
`Fiber::suspend()` が呼び出されると、現在のZend VMの状態(どこまで実行したか、ローカル変数は何か)が安全に退避され、制御権がイベントループ(呼び出し元)に返されます。そして `Fiber::resume()` で戻ってきたとき、VMは中断した正確な命令(Opcode)の位置から再開します。
これは、OSやランタイムにスケジューリングを丸投げするプリエンプティブ(占有型)ではなく、「開発者が自らスイッチを押す」という、極めて制御性の高い仕組みです。
—
2. アクターモデルとは何か?(Scala/Akkaの世界観)
対して、ScalaのAkkaなどで広く知られる「アクターモデル」は、並行処理に対する全く異なるアプローチです。
アクターモデルの基本哲学は、「共有メモリを一切持たず、メッセージの非同期送受信だけで協調動作する独立した演算単位(アクター)」です。
- 状態の完全なカプセル化: 各アクターは自分自身のプライベートな状態を持ち、他のアクターから直接読み書きすることはできません。
- メールボックス: アクターへの入力はすべて「メッセージ」としてメールボックス(キュー)に蓄積され、アクター自身が一つずつ順次(シリアルに)処理します。
- 「Let it crash(クラッシュさせよ)」思想: 内部で例外が発生しても、親アクター(スーパーバイザー)がそれを検知して再起動・修復するため、システム全体が停止しません。
アクターモデルは、分散システムや数百万の同時接続をさばく超高負荷なリアルタイムシステムにおいて、その真価を発揮します。
—
3. 徹底比較:Fiber vs アクターモデル
では、この2つをエンタープライズの文脈で比較してみましょう。
| 比較項目 | PHP Fiber(協調的マルチタスク) | アクターモデル(例: Akka) |
| :— | :— | :— |
| 実行制御 | プログラマが明示的に `suspend`/`resume` を制御 | ランタイム/フレームワークが完全に自動制御 |
| 並行性の単位 | コールスタック(軽量な実行コンテキスト) | アクター(状態 + メールボックス) |
| メモリ共有 | 可能(PHPのスコープに依存するため注意が必要) | 不可能(メッセージパッシングのみ) |
| エコシステム(PHP) | Revolt、Ampersand、ReactPHPなどのイベントループ基盤 | PHPネイティブでは準拠フレームワークが稀(Swooleの一部機能で近いことは可能) |
| 向いている用途 | I/Oバウンドな非同期処理(HTTPリクエスト、DBクエリ並行化) | 複雑なステート管理、分散システム、リアルタイムチャット等 |
ここで重要なのは、PHPにおけるFiberは「アクターモデルの代替」ではなく、アクターモデルのメールボックスやメッセージ処理機構を「実装するための基礎パーツ(プリミティブ)」として使えるという点です。
—
4. エンタープライズPHPにおける実践:Fiberでアクター風の非同期タスクを模倣する
百聞は一見に如かず。PHP 8.1以降のFiberを使って、「メッセージを受け取り、非同期に処理を抱えるアクター的な構造」を極めてシンプルなコードで実装してみましょう。
以下のコードは、外部APIへのリクエストをノンブロックで並行処理しつつ、各タスクをカプセル化する設計のイメージです。
/
class AsyncWorkerActor
{
private Fiber $fiber;
private string $status = ‘IDLE’;
private mixed $result = null;
public function __construct(int $workerId, string $targetUrl)
{
// Fiberの内部で実行される処理を定義
$this->fiber = new Fiber(function () use ($workerId, $targetUrl) {
$this->status = ‘RUNNING’;
echo “[Worker #{$workerId}] 処理開始: {$targetUrl}\n”;
// 模擬的なI/Oブロック(実際にはCURLやReactPHP等の非同期I/Oに置き換える)
// Fiber内での協調的な中断ポイント
$data = $this->simulateNonBlockingHttpCall($targetUrl);
$this->status = ‘COMPLETED’;
return $data;
});
}
public function start(): void
{
if ($this->fiber->isSuspended()) {
$this->fiber->resume();
} else {
$this->fiber->start();
}
}
public function suspend(): void
{
if ($this->fiber->isStarted() && ! $this->fiber->isTerminated()) {
// 必要に応じて外側から中断を促すフック
$this->status = ‘SUSPENDED’;
}
}
public function isFinished(): bool
{
return $this->fiber->isTerminated();
}
public function getResult(): mixed
{
return $this->fiber->getReturn();
}
private function simulateNonBlockingHttpCall(string $url): string
{
// 本来はここでイベントループに委譲し、レスポンスを待つ間に別のFiberに処理を譲る
// ここでは擬似的にsleepを使用していますが、アーキテクチャ上はI/O待ちを模しています
usleep(500000); // 0.5秒のI/O待ちを想定
return “Response from {$url}”;
}
}
// — 実行制御(簡易イベントループのシミュレーション) —
$actors = [
new AsyncWorkerActor(1, ‘https://api.example.com/data-1’),
new AsyncWorkerActor(2, ‘https://api.example.com/data-2’),
new AsyncWorkerActor(3, ‘https://api.example.com/data-3’),
];
echo “=== 並行処理タスク(Fiber)開始 ===\n”;
// 全アクターを起動(最初のサスペンドポイントまで実行)
foreach ($actors as $actor) {
$actor->start();
}
// すべての処理が完了するまでイベントループを回す
while (true) {
$activeCount = 0;
foreach ($actors as $actor) {
if (! $actor->isFinished()) {
$activeCount++;
// 実際のプロダクションでは、ここでRevoltなどのイベントループが
// ストリームの読み込み可能イベントなどを検知して resume() を呼びます。
$actor->start();
}
}
if ($activeCount === 0) {
break;
}
}
echo “=== すべてのタスクが完了しました ===\n”;
foreach ($actors as $index => $actor) {
$id = $index + 1;
echo “Worker #{$id} Result: ” . $actor->getResult() . “\n”;
}
このコードが教えてくれること
このコードのように、Fiberを使うことで「処理を途中で中断し、別の処理にコンテキストを切り替え、後から再開する」という非同期処理の根幹を、PHPの構文木(AST)とZend VMの上で直接コントロールできるようになります。
—
5. エンタープライズシステムにおける最適な選択基準
では、実際のエンタープライズWebシステムの設計において、私たちはどのように並行処理モデルを選択すべきでしょうか? アーキテクトとしての判断基準を提示します。
1. 「フルスタックなアクターモデル」をPHPで自前実装してはいけない
ScalaやErlangのように、システム全体を厳密なアクターモデルで構築しようとすることは、PHPのライフサイクル(基本はリクエストごとにプロセスが破棄されるShared-Nothing)において車輪の再発明であり、デバッグ地獄を招きます。PHPでこれをやりたい場合は、常駐型の実行基盤である Swoole や RoadRunner を採用し、その上でイベント駆動の設計を取り入れるのが現実解です。
2. I/Oバウンドなボトルネックには「Fiberベースの非同期ライブラリ」を選ぶ
もしあなたのエンタープライズPHPアプリケーション(LaravelやSymfonyなど)が、外部マイクロサービスへの大量のHTTPリクエスト、並行したDBクエリ、非同期のログ送信などで苦しんでいるなら、コードをアクターモデルに書き換える必要はありません。
Revolt や Amp (v3) といった、Fiberを基盤にしたモダンな非同期ライブラリを導入してください。これにより、ビジネスロジックを大きく変えることなく、スループットを劇的に向上させることができます。
3. ステートフルな複雑なドメインには、外部ブローカー(Redis / Kafka)を組み合わせる
PHPのワーカープロセス自体に複雑なメモリ上のステート(状態)を持たせるアクターモデル的な設計は、マルチプロセス環境(PHP-FPM)と非常に相性が悪いです。状態管理が必要な複雑なワークフローは、PHPのプロセス内だけで解決しようとせず、RedisやRabbitMQ、Kafkaなどのメッセージキュー・ステートストアを外側に置き、PHPのFiberはその非同期クライアントとして振る舞うのが、最も堅牢(ロバスト)でスケーラブルなエンタープライズアーキテクチャとなります。
—
まとめ
PHPのFiberは、私たちに「言語コアレベルでの非同期処理のコントロール」という強力な翼を与えてくれました。
アクターモデルのような壮大な分散並行処理の思想をそのままPHPに持ち込むのはオーバースペックであることが多いですが、その設計思想(状態の分離、メッセージングの重要性)を理解していれば、Fiberを用いたコードの設計品質は圧倒的に洗練されます。
「なぜここで処理が中断されるのか」「Zend VMのスタックはどう動いているのか」。
その裏側のメカニズムを脳内で美しくトレースできた時、あなたの書くPHPコードは、単なるスクリプトの域を超え、堅牢なエンタープライズ・アーキテクチャへと昇華するはずです。
さあ、次のシステム設計では、どのFiberを宙に舞わせますか?