この記事では、最初に、yield の使用法とジェネレーターのインターフェイスに焦点を当てて、ジェネレーターの概念を紹介します。コルーチン編では、コルーチンの原理やPHPコルーチンプログラミングで注意すべき事項について簡単に説明します。
PHP は 5.5 からジェネレーター (Generator) を導入し、これに基づいてコルーチン プログラミングを実現できます。この記事はジェネレーターのレビューから始まり、その後コルーチン プログラミングに移ります。
Generator は、イテレータ インターフェイスを実装するデータ型です。ジェネレーター インスタンスは new を通じて取得できません。また、ジェネレーター インスタンスを取得するための静的メソッドはありません。ジェネレーター インスタンスを取得する唯一の方法は、ジェネレーター関数 (yield キーワードを含む関数) を呼び出すことです。ジェネレーター関数を直接呼び出すとジェネレーター オブジェクトが返され、ジェネレーターの実行中に関数内のコードの実行が開始されます。
最初にコードに移動して、yield とジェネレーターを直感的に体験します。
# generator1.php function foo() { exit('exit script when generator runs.'); yield; } $gen = foo(); var_dump($gen); $gen->current(); echo 'unreachable code!'; # 执行结果 object(Generator)#1 (0) { } exit script when generator runs.
foo
関数には yield
キーワードが含まれており、ジェネレーター関数に変換されます。 。 foo
を呼び出すと、関数本体内のコードは実行されませんが、代わりにジェネレーター インスタンスが返されます。ジェネレーターの実行後、foo
関数内のコードが実行され、スクリプトが終了します。
名前が示すように、ジェネレーターを使用してデータを生成できます。データを生成する方法が他の関数と異なるだけです: ジェネレーターは return
ではなく yield
を介してデータを返します; yield
がデータを返した後、ジェネレーター関数は破棄されます。操作を一時停止するだけで、将来一時停止から再開できます。ジェネレーターは 1 回実行すると 1 つのデータ (のみ) を返し、複数回実行すると複数のデータを返します。ジェネレーターはデータを取得するために呼び出されるのではなく、ジェネレーター内のコードはそこに存在します。いわゆる「毎回移動」とは、ジェネレーターがデータを生成する方法を指します。
ジェネレーターは反復子インターフェイスを実装しています。ジェネレーター データを取得するには、foreach
ループまたは手動の current/next/valid
を使用できます。次のコードは、データの生成と走査を示しています。
# generator2.php function foo() { # 返回键值对数据 yield "key1" => "value1"; $count = 0; while ($count < 5) { # 返回值,key自动生成 yield $count; ++ $count; } # 不返回值,相当于返回null yield; } # 手动获取生成器数据 $gen = foo(); while ($gen->valid()) { fwrite(STDOUT, "key:{$gen->key()}, value:{$gen->current()}\n"); $gen->next(); } # foreach 遍历数据 fwrite(STDOUT, "\ndata from foreach\n"); foreach (foo() as $key => $value) { fwrite(STDOUT, "key:$key, value:$value\n"); }
yield
キーワードはジェネレーターの核心であり、これにより通常の関数をジェネレーターに区別 (進化) させることができます。機能。 yield
は「あきらめる」という意味です。プログラムが yield
ステートメントまで実行すると、実行が一時停止され、CPU が放棄され、呼び出し元に制御が戻ります。次の実行は、次の実行から継続されます。中断ポイントを実装します。制御が呼び出し元に戻ると、yield
ステートメントは呼び出し元に値を返すことができます。 generator2.php
このスクリプトは、yield 戻り値の 3 つの形式を示しています。
yield $key => $value: データのキーと値を返します。
yield $value: データを返し、キーはシステムによって割り当てられます;
yield: null 値を返し、キーはシステムによって割り当てられます。
yield
関数をいつでも一時停止し、実行を継続し、呼び出し元にデータを返すことができます。実行を継続するために外部データが必要な場合、この作業はジェネレーターの send
関数によって提供されます。yield
の左側に表示される変数は ## から変数を受け取ります。 #send 値。一般的な
send 関数の使用例を見てみましょう:
function logger(string $filename) { $fd = fopen($filename, 'w+'); while($msg = yield) { fwrite($fd, date('Y-m-d H:i:s') . ':' . $msg . PHP_EOL); } fclose($fd); } $logger = logger('log.txt'); $logger->send('program starts!'); // do some thing $logger->send('program ends!');
send ジェネレーターと外部の間で双方向のデータ通信を行う機能:
yieldデータを返す;
send 操作を継続するためのサポート データを提供します。
send によりジェネレーターは実行を継続できるため、この動作はイテレータの
next インターフェイスと似ています。ここで、
next は
send(null と同等です) )。
$string = yield $data; 式は PHP7 より前では無効であり、括弧が必要です:
$string = (yield $data);
値を返せません。PHP7 以降では値を返し、ジェネレーター getReturn の
を渡すことができます。戻り値を取得します。
構文を追加します。
を呼び出すことはできません。
のブログ投稿を読むことをお勧めします: PHP ジェネレーターは活発で、面白く、理解しやすいです。 コルーチン プログラミング
キーワードを使用すると、関数にこの機能を持たせることができます。コルーチンプログラミングに使用できます。 プロセス、スレッド、コルーチン
コルーチンは「ユーザー モード スレッド」とみなすことができ、ユーザー プログラムにスケジューリングを実装する必要があります。スレッドとプロセスは、「プリエンプティブ」方式で交互に実行されるようにオペレーティング システムによってスケジュールされ、コルーチンは積極的に CPU を放棄して「ネゴシエートされた」方式で交互に実行されます。コルーチンは非常に軽量で、コルーチンの切り替えにスレッド切り替えが不要で実行効率が高く、数が多いほどコルーチンの利点が反映されます。
ジェネレーターによって実装されるコルーチンはスタックレス コルーチンです。つまり、ジェネレーター関数には関数フレームのみがあり、実行時に呼び出し元のスタックにアタッチされます。強力なスタックフル コルーチンとは異なり、ジェネレーターは一時停止後のプログラムの方向を制御できず、制御を呼び出し元に受動的に返すことしかできません。ジェネレーターはコルーチン全体ではなく、それ自体に割り込むことしかできません。もちろん、ジェネレーターの利点は、効率が高く (一時停止時にプログラム カウンターを保存するだけで済みます)、実装が簡単であることです。
PHP でのコルーチン プログラミングといえば、ブラザー ニアオが転載 (翻訳) したこのブログ投稿を読んだことがある人がほとんどだと思います: コルーチンを使用して PHP タスク スケジューリングで複数の機能を実現する。オリジナルの著者である nikic は、PHP の中心的な開発者であり、ジェネレーター関数の開始者および実装者です。ジェネレーターとそれに基づくコルーチン プログラミングについて詳しく知りたい場合は、ジェネレーターに関する nikic の RFC と Niaoge の Web サイトの記事が必読です。
まず、ジェネレーターに基づいたコルーチンの動作方法を見てみましょう。コルーチンは協調的に動作します。つまり、コルーチンは積極的に CPU を放棄して、複数のタスクを交互に実行します (つまり、同時マルチタスクですが、並列ではありません)。 ; ジェネレーターはコルーチンとみなすことができ、yield
ステートメントが実行されると、CPU 制御は呼び出し元に戻り、呼び出し元は他のコルーチンまたは他のコードを実行し続けます。
バード兄弟のブログを理解することの難しさを見てみましょう。コルーチンは非常に軽量であり、数千のコルーチン (ジェネレーター) がシステム内に同時に存在できます。オペレーティング システムはコルーチンをスケジュールしません。コルーチンの実行を調整する作業は開発者にあります。ニアオ兄弟の記事には、コルーチン プログラミングがほとんどない (コルーチンを書くということは主にジェネレーター関数を書くことを意味します) と書かれているため、コルーチンの部分を理解していない人もいますが、彼らはコルーチン スケジューラー (スケジューラーまたはカーネル) の実装に多くの時間を費やしています。オペレーティング システムを管理し、すべてのコルーチンに対して公平なスケジューリングを実行します。 PHP 開発の一般的な考え方は次のとおりです。これらのコードを作成すると、PHP エンジンがコードを呼び出して期待される結果が得られます。コルーチン プログラミングでは、作業を実行するコードを記述するだけでなく、これらのコードにいつ動作するかを指示するコードを記述することも必要です。作者の考えをよく理解していないと当然理解は難しくなります。独自にスケジュールする必要があるため、ネイティブ コルーチン (async/await 形式) と比較したジェネレーター コルーチンの欠点となります。
コルーチンとは何なのか理解できたところで、何に使えるのでしょうか?コルーチンは連携してCPUを効率的に利用するために自らCPUを放棄しますが、当然放棄するタイミングはプログラムがブロックされたときです。プログラムはどこでブロックするのでしょうか?ユーザー モード コードがブロックされることはほとんどありません。ブロックは主にシステム コールによって発生します。システム コールの大部分は IO であるため、コルーチンの主なアプリケーション シナリオはネットワーク プログラミングです。プログラムのパフォーマンスと同時実行性を高めるには、プログラムはブロックせずに非同期で実行する必要があります。非同期実行には通知とコールバックが必要なため、コールバック関数を作成しても「コールバック地獄」の問題は避けられません。コードの可読性が低く、プログラムの実行プロセスがコールバック関数の層に分散されます。コールバック地獄を解決するには、主に Promise とコルーチンの 2 つの方法があります。コルーチンは同期方式でコードを作成できるため、高パフォーマンスのネットワーク プログラミング (IO 集中型) で推奨されます。
PHP でのコルーチン プログラミングを振り返ってみましょう。 PHP では、ジェネレーターに基づいてコルーチン プログラミングが実装されるため、RecoilPHP
や Amp
などのコルーチン フレームワークを使用することをお勧めします。これらのフレームワークにはすでにスケジューラーが記述されており、その上でジェネレーター関数を直接開発すると、カーネルが自動的に実行をスケジュールします (関数をコルーチン モードで実行するようにスケジュールしたい場合は、単に yield
を関数本体です)。コルーチン プログラミングに yield
メソッドを使用したくない場合は、golang に似たコルーチン プログラミング エクスペリエンスを実現し、開発効率を高めることができる swoole
またはその派生フレームワークをお勧めします。 PHPの。
オリジナルの PHP コルーチン プログラミングを使用したい場合は、Niao Ge のブログにあるものと同様のスケジューラが不可欠です。スケジューラはコルーチンの実行をスケジュールし、コルーチンが中断された後、制御はスケジューラに戻ります。したがって、スケジューラは常にメイン (イベント) ループ内にある必要があります。つまり、CPU がコルーチンを実行していないときは、スケジューラ コードを実行する必要があります。コルーチンなしで実行する場合、スケジューラは CPU の消費を避けるために自身をブロックし (Niao Ge のブログでは組み込みの select
システム コールを使用しています)、イベントの到着を待ってから、対応するコルーチンを実行する必要があります。プログラムの実行中は、スケジューラーのブロックを除き、コルーチンは実行中にブロック API を呼び出すべきではありません。
コルーチン プログラミングにおける yield
の主な機能は、戻り値を気にせずに制御を渡すことです (基本的に yield
戻り値次回の実行時に直接 send
になります)。制御の転送のタイミングとコルーチンの動作に焦点を当てる必要があります。
さらに、コルーチンは非同期とはほとんど関係がなく、実行環境のサポートにも依存することを説明する必要があります。従来のPHP動作環境では、promise/coroutineを利用しても同期的にブロックされてしまいます。コルーチン フレームワークがどれほど優れていても、sleep はもはや使いやすいものではありません。たとえて言えば、JavaScript が Promise/Async テクノロジを使用していない場合でも、JavaScript は非同期であり、ノンブロッキングです。
await に似たコルーチン プログラミングを実装できます。関連するコードは Github に多数あるため、この記事では説明しません。
PHP の Output_buffering の詳細な紹介、outputbuffering_PHP チュートリアル
以上がphp のコルーチンの詳細な紹介 (コード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。