PHP 8.1 Fibers
ネイティブのFibersで実行を一時停止・再開します。
「PHP 8.1 Fibers」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
Fibersがもたらすもの
PHP 8.1ではFibersが追加されました。これは、実行中のコールスタックを一時停止し、まったく別の場所から後で再開できるコアプリミティブです。Fiberは完全に独立したスタックであり、任意の位置で一時停止し、スタックを巻き戻すことなく続行できます。これが、ライブラリがPromiseチェーンを同期コードのように見えるawaitに置き換えるために欠けていた要素です。ReactPHPとAmpの最新APIは、この仕組みを利用しています。
最初のFiber
コールバックでFiberを作成し、start()して、自身をsuspend()させます。中断点で呼び出し元に制御が戻り、resume()でまさにその位置から続行します。
<?php
$fiber = new Fiber(function (): void {
echo "A: inside fiber\n";
$received = Fiber::suspend('paused');
echo "C: resumed with '$received'\n";
});
$value = $fiber->start(); // prints A, returns 'paused'
echo "B: fiber suspended, got '$value'\n";
$fiber->resume('hello'); // prints C
echo "D: fiber done\n";4つの基本メソッド
APIは小規模です。
$fiber->start(...$args)— 実行を開始し、最初に一時停止したときの値を返します。Fiber::suspend($value)— 現在のFiberを一時停止し、$valueを再開側に渡します。$fiber->resume($value)— 実行を続行し、$valueがsuspend()の戻り値になります。$fiber->getReturn()— Fiberが終了した後のコールバックの戻り値です。
一時停止の境界を越えて、値が双方向に受け渡されます。
双方向の値の受け渡し
Fiberはリクエストを外部に渡し、応答を受け取ることができます。これは、イベントループが一時停止中のタスクにI/O結果を渡す仕組みの中核です。
<?php
$fiber = new Fiber(function (): int {
$a = Fiber::suspend('need first number');
$b = Fiber::suspend('need second number');
return $a + $b;
});
$fiber->start(); // suspends: 'need first number'
$fiber->resume(10); // a = 10, suspends: 'need second number'
$fiber->resume(32); // b = 32, fiber returns
echo $fiber->getReturn(), PHP_EOL; // 42Fiberの状態とイントロスペクション
Fiberのライフサイクルを確認できます。終了したFiberに対してresume()を呼び出すとFiberErrorが発生するため、スケジューラを構築するときはこれらのチェックで保護してください。
<?php
$fiber = new Fiber(function () { Fiber::suspend(); });
var_dump($fiber->isStarted()); // false
$fiber->start();
var_dump($fiber->isStarted()); // true
var_dump($fiber->isSuspended()); // true
var_dump($fiber->isRunning()); // false (we're outside it)
$fiber->resume();
var_dump($fiber->isTerminated()); // true小さなスケジューラを構築する
Fibersとキューを組み合わせると、協調的なスケジューラになります。各タスクは制御を譲るために一時停止し、スケジューラはすべてのタスクが完了するまでラウンドロビン方式で実行します。これは、すべての非同期ランタイムの内部にある基本構造です。
<?php
$tasks = [];
foreach (['X', 'Y', 'Z'] as $name) {
$tasks[] = new Fiber(function () use ($name) {
for ($i = 1; $i <= 2; $i++) {
echo "$name step $i\n";
Fiber::suspend(); // give others a turn
}
});
}
foreach ($tasks as $t) { $t->start(); }
while ($tasks) {
foreach ($tasks as $k => $t) {
if ($t->isTerminated()) { unset($tasks[$k]); continue; }
$t->resume();
}
}境界を越えた例外
throwInto()(APIでは$fiber->throw()としてthrow()と呼ばれます)は、一時停止点に例外を注入します。これは、ループがI/O操作の失敗を待機中のタスクに伝える方法です。例外はFiberが停止していた正確な場所で発生します。
<?php
$fiber = new Fiber(function () {
try {
Fiber::suspend();
} catch (RuntimeException $e) {
echo 'caught: ' . $e->getMessage() . PHP_EOL;
}
});
$fiber->start();
$fiber->throw(new RuntimeException('io failed')); // prints: caught: io failedFibersとジェネレータ
ジェネレータ(yield)も一時停止しますが、最上位の関数だけです。ジェネレータでネストしたヘルパーから待機するには、スタックを上までずっとyield fromする必要があり、すべての関数にその影響が及びます。Fibersはスタック全体を一時停止します。そのため、深くネストした呼び出しでも、呼び出し元に知られずにFiber::suspend()を実行できます。これが、awaitをyieldではなく通常の関数呼び出しにできる理由です。
<?php
function deep(): string {
// No yield needed in the call chain — suspends the whole fiber
return Fiber::suspend('awaiting from deep');
}
$f = new Fiber(fn() => print(deep() . PHP_EOL));
$f->start();
$f->resume('value injected at depth');一時停止に関する制限
重要なルールが1つあります。Fiber::suspend()を実行できるのはFiberの内部だけです。メインスレッドから一時停止した場合や、FiberをサポートしていないCレベルのコールバック(レガシーなusortコンパレータの一部や、特定の内部コールバックなど)をまたいで一時停止した場合は、FiberError: Cannot suspend outside of a fiberが発生します。ライブラリは、すべてのユーザーコードがループによって駆動されるFiberの内部で実行されるようにすることで、この問題を隠します。
<?php
try {
Fiber::suspend(); // main thread — illegal
} catch (FiberError $e) {
echo $e->getMessage(), PHP_EOL; // Cannot suspend outside of a fiber
}ライブラリによるFibersの利用方法
アプリケーションコードで生のFibersを直接書くことはほとんどありません。Amp v3やReactPHP(Fibers経由)のようなフレームワークは、Fibersをラップします。await($promise)は内部でFiber::suspend()を呼び出し、イベントループに継続処理を登録し、I/Oが完了するとresume()でFiberを再開します。コードは同期的に見えますが、停止とスケジューリングはランタイムが処理します。
<?php
// Conceptual: what an await() helper does under the hood
function await(Promise $p): mixed {
$fiber = Fiber::getCurrent() ?? throw new Error('await() outside fiber');
$p->then(fn($v) => $fiber->resume($v),
fn($e) => $fiber->throw($e));
return Fiber::suspend(); // parked until the promise settles
}メモリとライフサイクルに関する注意点
各Fiberは独自のスタックを確保するため、数十万のFiberを生成するとメモリコストが発生します(ただしOSスレッドよりはるかに軽量です)。何かがFiberを参照し続けている場合、永遠に一時停止したFiberはガベージコレクションされません。これは長時間稼働するサーバーで発生する見落としやすいリークです。また、Fiberのコールバックが捕捉されない例外をスローすると、その例外はresume()/start()の呼び出し元まで伝播するため、スケジューラは再開処理をtry/catchで囲む必要があります。
<?php
$fiber = new Fiber(function () {
throw new RuntimeException('boom inside fiber');
});
try {
$fiber->start(); // exception surfaces here, at the resumer
} catch (RuntimeException $e) {
echo 'scheduler caught: ' . $e->getMessage() . PHP_EOL;
}確認
非同期処理において、Fibersがジェネレータより優れている主な点は何ですか。
まとめ
Fibersは、PHPの低レベルな並行処理プリミティブです。
start()、Fiber::suspend()、resume()、getReturn()によって、完全なコールスタックを一時停止・続行し、値を双方向に受け渡せます。- 状態メソッド(
isSuspended、isTerminated)とthrow()によって、協調的なスケジューラを構築し、I/Oエラーを注入できます。 - ジェネレータとは異なり、Fiberはどの深さからでも一時停止できるため、透過的な
await()が可能になります。 - 一時停止できるのはFiberの内部だけです。AmpやReactPHPなどのフレームワークはFibersをラップするため、非同期コードを同期コードのように記述できます。
よくある質問
「PHP 8.1 Fibers」レッスンは無料ですか?
はい。「PHP 8.1 Fibers」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「PHP 8.1 Fibers」で何を学びますか?
ネイティブのFibersで実行を一時停止・再開します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「PHP 8.1 Fibers」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- PHPの並行実行モデル
- PHP 8.1 Fibers
- ReactPHPによるイベントループ
- Swooleによる高性能サーバー