(CJP) 少ないほうがいいですね。 あなたは前にそれを聞いたことがありますよね? 覚えておいてください、私はあなたに何かを見せるつもりです。
PHP コードを並行して実行するための優れた堅牢なソリューションは、すでにいくつかあります。 それでも、独自の実装を作成しました。 理由を説明したい。 まず、シーンを設定しましょう。PHP コードを並列で実行したいと考えています。 ここに私のユースケースのいくつかがあります:
- PHPUnit テストの実行時に、コマンドバスの競合状態をテストします。
- 一連の HTTP リクエストを並行して実行する。 そしてまた
- 私の静的ジェネレーターが複数のプロセスで動作できるようにすることで、このブログをより速く生成することができます。
私のユース ケースには 2 つの共通の要件があります。任意の数の関数を並列に実行することと、それらすべてが完了するまで待機することです。 現在利用可能なソリューションを見てみましょう。
AmpPHP parallel-functions というパッケージがあります。 次のようになります。
use Amp\Promise;
use function Amp\ParallelFunctions\parallelMap;
$values = Promise\wait(
parallelMap([1, 2, 3], function ($time)
\sleep($time);
return $time * $time;
)
);
私の使用例では、この実装にはいくつかの問題があります。
これは、より複雑な非同期作業には非常に適していますが、私にとってはオーバーヘッドにすぎません。
Amp の API とその関数の使用は、私には非常にぎこちなく感じられますが、それはむしろ主観的なものです。 そして最後に
子プロセスにフレームワークが必要な場合は、手動で起動する必要があります。
に進む ReactPHP、Amp のようなすぐに使えるソリューションはありませんが、低レベルのコンポーネントを提供します。
$loop = React\EventLoop\Factory::create();
$process = new React\ChildProcess\Process(‘php child-process.php’);
$process->start($loop);
$process->stdout->on(‘data’, function ($chunk)
echo $chunk;
);
$process->on(‘exit’, function($exitCode, $termSignal)
echo ‘Process exited with code ‘ . $exitCode . PHP_EOL;
);
$loop->run();
この実装に関するいくつかの注意事項:
ReactPHP では、常に手動でイベント ループを作成する必要がありますが、これも私にとってはオーバーヘッドです。
それらはプロミスでも機能します。 そして最後に
プロセスを並行して実行するための最低限のインフラしか提供していないため、多くの手動セットアップ作業が必要です。
最後に、 ガズル 同時リクエスト:
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
$client = new Client([‘base_uri’ => ‘http://httpbin.org/’]);
$promises = [
‘image’ => $client->getAsync(‘/image’),
‘png’ => $client->getAsync(‘/image/png’),
‘jpeg’ => $client->getAsync(‘/image/jpeg’),
‘webp’ => $client->getAsync(‘/image/webp’)
];
$responses = Promise\Utils::unwrap($promises);
ここでも、Promise のオーバーヘッドがあります。 しかし、もっと重要なこと
Guzzle は HTTP リクエストでのみ動作し、これは私の問題の一部しか解決しません。
上記のすべての中で、Amp のアプローチは、私の単純なユースケースに対してまだかなりのオーバーヘッドがあるというわけではありませんが、私の好みです。 正直なところ、私がやりたかったことは、いくつかの機能を並行して実行し、それらがすべて完了するまで待つことでした。 フレームワークが使用している特定の API に関するドキュメントを探すことに煩わされたくありません。 ここで関数をインポートする必要がありましたか? 約束を解く方法は? すべてが完了するのを待つ方法は?
上記の例はすべて、10% のケースで多くの制御が必要な場合の優れたソリューションですが、90% のケースで 1 つのことをできるだけ簡単に実行したい場合はどうでしょうか?
少ないほうがいいですね。 ソフトウェア設計では、それを忘れがちです。 誰かがそれを必要とするかもしれない場合に備えて、私たちはソリューションを過度に複雑にし、90% のユースケースを忘れています。 開発者はフレームワークの使用方法を理解するためにドキュメントを調べたり、一般的なケースを機能させるために定型文をたくさん書かなければならないため、フラストレーションにつながります。
以上のことから、関数を並列に実行して結果を待つという 1 つの単純な目標を持つ別のライブラリを作成することにした理由がわかりました。 外観は次のとおりです。
$rssFeeds = Fork::new()
->run(
fn () => file_get_contents(‘https://stitcher.io/rss’),
fn () => file_get_contents(‘https://freek.dev/rss’),
fn () => file_get_contents(‘https://spatie.be/rss’),
);
以上です。 それは 1 つの仕事をし、それをうまく行います。 誤解しないでください。単純な機能しか提供しない単純な API があるからではありません。 さらにいくつかの例を紹介しましょう。
並列関数は、オブジェクトを含め、何でも返すことができます。
$dates = Fork::new()
->run(
fn () => new DateTime(‘2021-01-01’),
fn () => new DateTime(‘2021-01-02’),
);
新しいプロセスの代わりにプロセス フォークを使用します。つまり、すべての子プロセスでフレームワークを手動で起動する必要はありません。
[$users, $posts, $news] = Fork::new()->run(
fn () => User::all(),
fn () => Post::all(),
fn () => News::all(),
);
もう少しセットアップ作業を行う必要がある場合に備えて、バインディングの前後を許可します。 前の例では、Laravel は動作する前に実際に子プロセスでデータベースに再接続する必要があります。
[$users, $posts, $news] = Fork::new()->before(fn () => DB::connection(‘mysql’)->reconnect())
->run(
fn () => User::all(),
fn () => Post::all(),
fn () => News::all(),
);
最後に、バインディングの前と後は、子プロセスと親プロセスの両方で実行できます。 また、個々の関数出力をパラメーターとしてこれらに渡す方法にも注意してください after コールバック:
Fork::new()
->after(
child: fn () => DB::connection(‘mysql’)->close(),
parent: fn (int $amountOfPages) =>
$this->progressBar->advance($amountOfPages),
)
->run(
fn () => Pages::generate(‘1-20′),
fn () => Pages::generate(’21-40′),
fn () => Pages::generate(’41-60’),
);
もちろん、このパッケージでは実行できないことがいくつかあります。
同時プロセスの量を管理するプールはありません。必要に応じて担当します。
約束はありません。
pcntl は Windows では機能せず、Web 要求では実行されません。
舞台裏での例外処理はありません。子が失敗すると、例外がスローされ、プロセス フローが停止します。
言い換えれば、いくつかの関数を並行して実行し、それを完了したいという 90% のケースに最適なソリューションです。 それ以上のものが必要な場合は、上記のソリューションから始めることをお勧めします。 と呼ばれる私たちの別のパッケージもあります spatie/async これは promise では機能しませんが、プール構成と広範な例外処理を提供します。
詳細を知りたい場合、または自分でパッケージを試してみたい場合は、GitHub でチェックアウトできます。 spatie/fork.
tpyoに気づきましたか? PR を送信して修正することができます。 このブログの最新情報を知りたい場合は、私をフォローしてください。 ツイッター または私のニュースレターを購読してください:
少ないほうがいいですね。 これは、コーディング時の私の基本原則の 1 つです。 私は、見るたびに使い方がわからなくなる高度に構成可能なフレームワークよりも、何かを 1 つの方法で実行することを強いられるが、常に機能するコードを好みます。 多くの開発者は、高度な構成可能性と拡張性の迷路に迷い込み、本来の目的を忘れてしまうことがよくあると思います。
このパッケージが、90% のカテゴリに分類される人々のグループに役立つことを願っています.