AMPHP は、ファイバーと同時実行性を念頭に置いて設計された PHP 用のイベント駆動型ライブラリのコレクションです。 amphp/amp
、特に、非同期プログラミングの基本的なプリミティブとして先物とキャンセルを提供します。現在、イベント ループ実装をamphp/amp
で出荷する代わりに Revolt を使用しています。
Amp は、PHP 8.1 に同梱されているファイバーを多用して、同期のブロッキング コードと同じように非同期コードを作成します。以前のバージョンとは異なり、ジェネレーター ベースのコルーチンやコールバックは必要ありません。スレッドと同様に、各ファイバーには独自の呼び出しスタックがありますが、ファイバーはイベント ループによって協調的にスケジュールされます。同時に実行するには、 Ampasync()
を使用します。
従来、PHP は逐次実行モデルに従っています。 PHP エンジンは、1 行ずつ順番に実行します。ただし、多くの場合、プログラムは同時に実行できる複数の独立したサブプログラムで構成されます。
データベースにクエリを実行する場合は、ブロッキング方式でクエリを送信し、データベース サーバーからの応答を待ちます。応答が得られたら、次の作業を開始できます。何もせずに座って待っているのではなく、すでに次のデータベース クエリを送信したり、API への HTTP 呼び出しを実行したりすることができます。普段I/Oの待ち時間に費やしている時間を有効活用しましょう!
Revolt では、このような同時 I/O 操作が可能です。コールバックを回避することで認知負荷を低く保ちます。私たちの API は他のライブラリと同じように使用できますが、内部でノンブロッキング I/O を使用するため、同時に動作する点が異なります。 Ampasync()
使用して同時に実行し、必要なときに必要なときにFuture::await()
使用して結果を待ちます。
PHP で同時実行性を実装するためのさまざまな手法が長年にわたって存在してきました。たとえば、PHP 5 で出荷されたコールバックやジェネレーターなどです。これらのアプローチは、「関数は何色ですか」という問題に悩まされていましたが、PHP 8.1 でファイバーを出荷することで解決しました。これらにより、複数の独立したコール スタックとの同時実行が可能になります。
ファイバーはイベント ループによって協調的にスケジュールされるため、コルーチンとも呼ばれます。常に 1 つのコルーチンのみが実行され、その間、他のすべてのコルーチンが一時停止されることを理解することが重要です。
コルーチンを、単一の CPU コアを使用して複数のプログラムを実行するコンピューターにたとえることができます。各プログラムは実行するタイムスロットを取得します。ただし、コルーチンはプリエンプティブではありません。彼らは固定された時間枠を取得できません。彼らは自発的にイベント ループへの制御を放棄する必要があります。
ブロッキング I/O 関数は、I/O の待機中にプロセス全体をブロックします。それらは避けたくなるでしょう。インストール ガイドをまだ読んでいない場合は、関数をブロックする効果を示す Hello World の例をご覧ください。 AMPHP が提供するライブラリは、I/O のブロックを回避します。
このパッケージは Composer の依存関係としてインストールできます。
composer require amphp/amp
このライブラリを使用する場合は、Revolt を使用してイベントをスケジュールする可能性が高く、依存関係として自動的にインストールされる場合でも、別途必要にする必要があります。
composer require revolt/event-loop
これらのパッケージは、PHP の非同期/同時アプリケーションの基本的な構成要素を提供します。これらの上に構築された多くのパッケージを提供しています。
amphp/byte-stream
ストリームの抽象化を提供しますamphp/socket
TLS を含む UDP および TCP のソケット層を提供します。amphp/parallel
複数の CPU コアを利用し、ブロック操作をオフロードするための並列処理を提供します。amphp/http-client
amphp/http-server
HTTP/1.1 および HTTP/2 アプリケーション サーバーを提供しますamphp/mysql
およびamphp/postgres
このパッケージには PHP 8.1 以降が必要です。拡張機能は必要ありません。
拡張機能は、アプリで多数の同時ソケット接続が必要な場合にのみ必要です。通常、この制限は最大 1024 個のファイル記述子に設定されます。
コルーチンは割り込み可能な関数です。 PHP では、ファイバーを使用して実装できます。
注: Amp の以前のバージョンでも同様の目的でジェネレーターが使用されていましたが、ファイバーはコール スタックのどこでも中断できるため、
Ampcall()
などの以前の定型文は不要になりました。
常に 1 つのファイバーだけが実行されます。コルーチンが一時停止すると、コルーチンの実行が一時的に中断され、他のタスクを実行できるようになります。タイマーが期限切れになるか、ストリーム操作が可能になるか、待機されているFuture
が完了すると、実行が再開されます。
コルーチンの低レベルの一時停止と再開は、Revolt のSuspension
API によって処理されます。
<?php
require __DIR__ . ' /vendor/autoload.php ' ;
use Revolt EventLoop ;
$ suspension = EventLoop:: getSuspension ();
EventLoop:: delay ( 5 , function () use ( $ suspension ): void {
print ' ++ Executing callback created by EventLoop::delay() ' . PHP_EOL ;
$ suspension -> resume ( null );
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
$ suspension -> suspend ();
print ' ++ Script end ' . PHP_EOL ;
Revolt イベント ループに登録されたコールバックはコルーチンとして自動的に実行されるため、安全に一時停止できます。イベント ループ API とは別に、 Ampasync()
使用して独立したコール スタックを開始できます。
<?php
use function Amp delay ;
require __DIR__ . ' /vendor/autoload.php ' ;
Amp async ( function () {
print ' ++ Executing callback passed to async() ' . PHP_EOL ;
delay ( 3 );
print ' ++ Finished callback passed to async() ' . PHP_EOL ;
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
delay ( 5 );
print ' ++ Script end ' . PHP_EOL ;
Future
、非同期操作の最終的な結果を表すオブジェクトです。次の 3 つの状態があります。
正常に完了したフューチャーは戻り値に似ていますが、エラーが発生したフューチャーは例外をスローすることに似ています。
非同期 API にアプローチする 1 つの方法は、操作の開始時に渡され、操作が完了すると呼び出されるコールバックを使用することです。
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
コールバックのアプローチにはいくつかの欠点があります。
そこで登場するのが先物です。これらは、他の戻り値と同様に返される結果のプレースホルダーです。呼び出し元は、 Future::await()
を使用して結果を待つか、1 つまたは複数のコールバックを登録するかを選択できます。
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
同時アプリケーションでは複数の Future が存在するため、それらすべてを待機することも、最初の Future だけを待機することもできます。
AmpFutureawait($iterable, $cancellation)
iterable
のすべてのFuture
オブジェクトを待機します。 Future
インスタンスの 1 つでエラーが発生した場合、操作はその例外で中止されます。それ以外の場合、結果は、 iterable
入力から完了値までのキーに一致する配列になります。
await()
コンビネータは、多くの非同期操作を同時に実行できるため、非常に強力です。 amphp/http-client
使用して複数の HTTP リソースを同時に取得する例を見てみましょう。
<?php
use Amp Future ;
use Amp Http Client HttpClientBuilder ;
use Amp Http Client Request ;
$ httpClient = HttpClientBuilder:: buildDefault ();
$ uris = [
" google " => " https://www.google.com " ,
" news " => " https://news.google.com " ,
" bing " => " https://www.bing.com " ,
" yahoo " => " https://www.yahoo.com " ,
];
try {
$ responses = Future await ( array_map ( function ( $ uri ) use ( $ httpClient ) {
return Amp async ( fn () => $ httpClient -> request ( new Request ( $ uri , ' HEAD ' )));
}, $ uris ));
foreach ( $ responses as $ key => $ response ) {
printf (
" %s | HTTP/%s %d %s n" ,
$ key ,
$ response -> getProtocolVersion (),
$ response -> getStatus (),
$ response -> getReason ()
);
}
} catch ( Exception $ e ) {
// If any one of the requests fails the combo will fail
echo $ e -> getMessage (), "n" ;
}
AmpFutureawaitAnyN($count, $iterable, $cancellation)
個々のエラーを許容する点を除いてawait()
と同じです。 iterable
内のちょうど$count
インスタンスが正常に完了すると、結果が返されます。戻り値は値の配列です。コンポーネント配列内の個々のキーは、評価のために関数に渡されるiterable
オブジェクトから保存されます。
AmpFutureawaitAll($iterable, $cancellation)
すべての Future を待機し、その結果を[$errors, $values]
配列として返します。
AmpFutureawaitFirst($iterable, $cancellation)
正常に完了したかエラーが発生したかに関係なく、最初に完了したFuture
アンラップします。
AmpFutureawaitAny($iterable, $cancellation)
最初に正常に完了したFuture
をアンラップします。
先物はいくつかの方法で作成できます。ほとんどのコードは、関数を受け取り、それを別のファイバーでコルーチンとして実行するAmpasync()
を使用します。
場合によっては、インターフェイスでFuture
返すように要求する場合もありますが、結果はキャッシュされているなどの理由ですぐに利用できます。このような場合、 Future::complete(mixed)
とFuture::error(Throwable)
使用して、すぐに完了するFuture
構築できます。
注:以下で説明する
DeferredFuture
API は、多くのアプリケーションでは必要としない高度な API です。可能な場合は、代わりにAmpasync()
またはコンビネータを使用してください。
AmpDeferredFuture
保留中のFuture
を完了する責任があります。 AmpDeferredFuture
を作成し、そのgetFuture
メソッドを使用して呼び出し元にAmpFuture
を返します。結果の準備ができたら、リンクされたDeferredFuture
でcomplete
またはerror
使用して、呼び出し元が保持するFuture
を完了します。
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
警告
DeferredFuture
オブジェクトを渡している場合は、何か間違ったことをしている可能性があります。これらは操作の内部状態であるはずです。
警告別の Future を使用して Future を完成させることはできません。このような場合は、
DeferredFuture::complete()
を呼び出す前にFuture::await()
を使用してください。
以下は、非同期値プロデューサーasyncMultiply()
がDeferredFuture
作成し、関連付けられたFuture
を呼び出し元に返す簡単な例です。
<?php // Example async producer using DeferredFuture
use Revolt EventLoop ;
function asyncMultiply ( int $ x , int $ y ): Future
{
$ deferred = new Amp DeferredFuture ;
// Complete the async result one second from now
EventLoop:: delay ( 1 , function () use ( $ deferred , $ x , $ y ) {
$ deferred -> complete ( $ x * $ y );
});
return $ deferred -> getFuture ();
}
$ future = asyncMultiply ( 6 , 7 );
$ result = $ future -> await ();
var_dump ( $ result ); // int(42)
キャンセルをサポートするすべての操作は、引数としてCancellation
のインスタンスを受け入れます。キャンセルは、登録ハンドラーがキャンセル要求をサブスクライブできるようにするオブジェクトです。これらのオブジェクトはサブオペレーションに渡されるか、オペレーション自体で処理する必要があります。
$cancellation->throwIfRequested()
使用すると、キャンセルが要求された後にCancelledException
で現在の操作を失敗させることができます。 throwIfRequested()
正常に機能しますが、一部の操作では代わりにコールバックを使用してサブスクライブする必要がある場合があります。 Cancellation::subscribe()
を使用して、発生する可能性のあるキャンセル要求をサブスクライブできます。
呼び出し元は、以下の実装のいずれかを使用してCancellation
を作成します。
注:キャンセルはあくまで勧告です。応答はとにかく処理する必要があり、まだキャッシュできるため、クエリの送信後に DNS リゾルバーはキャンセル要求を無視する場合があります。 HTTP クライアントは、接続を再利用するためにほぼ終了した HTTP リクエストを続行する可能性がありますが、続行する方が実際に中止するよりもコストが低いかどうかを知ることができないため、チャンク化されたエンコード応答を中止する可能性があります。
TimeoutCancellations
、指定された秒数が経過すると自動的にキャンセルされます。
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
SignalCancellation
指定されたシグナルが現在のプロセスによって受信された後、自動的にキャンセルされます。
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
DeferredCancellation
すると、メソッドを呼び出して手動でキャンセルできます。独自の実装を出荷する代わりに、どこかにカスタム コールバックを登録する必要がある場合は、これが推奨される方法です。呼び出し元のみがDeferredCancellation
にアクセスでき、 DeferredCancellation::cancel()
使用して操作をキャンセルできます。
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
NullCancellation
決してキャンセルされません。キャンセルはオプションであることが多く、通常はパラメーターを null 可能にすることで実装されます。 if ($cancellation)
のようなガードを回避するには、代わりにNullCancellation
使用できます。
$ cancellation ??= new NullCancellationToken ();
CompositeCancellation
を結合します。これらのキャンセルのいずれかがキャンセルされると、 CompositeCancellation
自体もキャンセルされます。
amphp/amp
他のすべてのamphp
パッケージと同様に、semver セマンティック バージョニング仕様に従います。
互換性のあるパッケージは、GitHub のamphp
トピックを使用する必要があります。
セキュリティ関連の問題を発見した場合は、問題トラッカーを使用する代わりに、 [email protected]
に電子メールを送信してください。
MIT ライセンス (MIT)。詳細については、 LICENSE
を参照してください。