ホームページ  >  記事  >  バックエンド開発  >  php のコルーチンの詳細な紹介 (コード)

php のコルーチンの詳細な紹介 (コード)

不言
不言オリジナル
2018-09-13 16:57:283846ブラウズ

この記事では、最初に、yield の使用法とジェネレーターのインターフェイスに焦点を当てて、ジェネレーターの概念を紹介します。コルーチン編では、コルーチンの原理やPHPコルーチンプログラミングで注意すべき事項について簡単に説明します。

PHP は 5.5 からジェネレーター (Generator) を導入し、これに基づいてコルーチン プログラミングを実現できます。この記事はジェネレーターのレビューから始まり、その後コルーチン プログラミングに移ります。

yield とジェネレータ

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 は「あきらめる」という意味です。プログラムが yield ステートメントまで実行すると、実行が一時停止され、CPU が放棄され、呼び出し元に制御が戻ります。次の実行は、次の実行から継続されます。中断ポイントを実装します。制御が呼び出し元に戻ると、yield ステートメントは呼び出し元に値を返すことができます。 generator2.phpこのスクリプトは、yield 戻り値の 3 つの形式を示しています。

  1. yield $key => $value: データのキーと値を返します。

  2. yield $value: データを返し、キーはシステムによって割り当てられます;

  3. 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 インターフェイスと似ています。ここで、nextsend(null と同等です) )

その他

  1. $string = yield $data; 式は PHP7 より前では無効であり、括弧が必要です: $string = (yield $data);

  2. ##PHP5 ジェネレーター関数は
  3. return

    値を返せません。PHP7 以降では値を返し、ジェネレーター getReturn を渡すことができます。戻り値を取得します。

  4. PHP7 は、ジェネレーターの委任を実装するために
  5. yield from

    構文を追加します。

  6. ジェネレーターは一方向の反復子であり、開始後に
  7. rewind

    を呼び出すことはできません。

  8. 概要

他のイテレータと比較して、ジェネレーターはパフォーマンスのオーバーヘッドが低く、コーディングが簡単であるという特徴があります。その役割は主に 3 つの側面に反映されます:

    データ生成 (プロデューサー)、イールドを通じてデータを返す;
  1. データ消費 (コンシューマー) 、送信からのデータを消費します;
  2. はコルーチンを実装します。
  3. ジェネレーターと PHP の基本的な使用法については、
2gua

のブログ投稿を読むことをお勧めします: PHP ジェネレーターは活発で、面白く、理解しやすいです。 コルーチン プログラミング

コルーチン (コルーチン) は、いつでも中断して再開できるサブルーチンです。

yield

キーワードを使用すると、関数にこの機能を持たせることができます。コルーチンプログラミングに使用できます。 プロセス、スレッド、コルーチン

スレッドはプロセスに属し、プロセスは複数のスレッドを持つことができます。プロセスはコンピュータにリソースを割り当てる最小単位であり、スレッドはコンピュータのスケジューリングと実行の最小単位です。プロセスとスレッドは両方ともオペレーティング システムによってスケジュールされます。

コルーチンは「ユーザー モード スレッド」とみなすことができ、ユーザー プログラムにスケジューリングを実装する必要があります。スレッドとプロセスは、「プリエンプティブ」方式で交互に実行されるようにオペレーティング システムによってスケジュールされ、コルーチンは積極的に CPU を放棄して「ネゴシエートされた」方式で交互に実行されます。コルーチンは非常に軽量で、コルーチンの切り替えにスレッド切り替えが不要で実行効率が高く、数が多いほどコルーチンの利点が反映されます。

ジェネレーターとコルーチン

ジェネレーターによって実装されるコルーチンはスタックレス コルーチンです。つまり、ジェネレーター関数には関数フレームのみがあり、実行時に呼び出し元のスタックにアタッチされます。強力なスタックフル コルーチンとは異なり、ジェネレーターは一時停止後のプログラムの方向を制御できず、制御を呼び出し元に受動的に返すことしかできません。ジェネレーターはコルーチン全体ではなく、それ自体に割り込むことしかできません。もちろん、ジェネレーターの利点は、効率が高く (一時停止時にプログラム カウンターを保存するだけで済みます)、実装が簡単であることです。

コルーチン プログラミング

PHP でのコルーチン プログラミングといえば、ブラザー ニアオが転載 (翻訳) したこのブログ投稿を読んだことがある人がほとんどだと思います: コルーチンを使用して PHP タスク スケジューリングで複数の機能を実現する。オリジナルの著者である nikic は、PHP の中心的な開発者であり、ジェネレーター関数の開始者および実装者です。ジェネレーターとそれに基づくコルーチン プログラミングについて詳しく知りたい場合は、ジェネレーターに関する nikic の RFC と Niaoge の Web サイトの記事が必読です。

まず、ジェネレーターに基づいたコルーチンの動作方法を見てみましょう。コルーチンは協調的に動作します。つまり、コルーチンは積極的に CPU を放棄して、複数のタスクを交互に実行します (つまり、同時マルチタスクですが、並列ではありません)。 ; ジェネレーターはコルーチンとみなすことができ、yield ステートメントが実行されると、CPU 制御は呼び出し元に戻り、呼び出し元は他のコルーチンまたは他のコードを実行し続けます。

バード兄弟のブログを理解することの難しさを見てみましょう。コルーチンは非常に軽量であり、数千のコルーチン (ジェネレーター) がシステム内に同時に存在できます。オペレーティング システムはコルーチンをスケジュールしません。コルーチンの実行を調整する作業は開発者にあります。ニアオ兄弟の記事には、コルーチン プログラミングがほとんどない (コルーチンを書くということは主にジェネレーター関数を書くことを意味します) と書かれているため、コルーチンの部分を理解していない人もいますが、彼らはコルーチン スケジューラー (スケジューラーまたはカーネル) の実装に多くの時間を費やしています。オペレーティング システムを管理し、すべてのコルーチンに対して公平なスケジューリングを実行します。 PHP 開発の一般的な考え方は次のとおりです。これらのコードを作成すると、PHP エンジンがコードを呼び出して期待される結果が得られます。コルーチン プログラミングでは、作業を実行するコードを記述するだけでなく、これらのコードにいつ動作するかを指示するコードを記述することも必要です。作者の考えをよく理解していないと当然理解は難しくなります。独自にスケジュールする必要があるため、ネイティブ コルーチン (async/await 形式) と比較したジェネレーター コルーチンの欠点となります。

コルーチンとは何なのか理解できたところで、何に使えるのでしょうか?コルーチンは連携してCPUを効率的に利用するために自らCPUを放棄しますが、当然放棄するタイミングはプログラムがブロックされたときです。プログラムはどこでブロックするのでしょうか?ユーザー モード コードがブロックされることはほとんどありません。ブロックは主にシステム コールによって発生します。システム コールの大部分は IO であるため、コルーチンの主なアプリケーション シナリオはネットワーク プログラミングです。プログラムのパフォーマンスと同時実行性を高めるには、プログラムはブロックせずに非同期で実行する必要があります。非同期実行には通知とコールバックが必要なため、コールバック関数を作成しても「コールバック地獄」の問題は避けられません。コードの可読性が低く、プログラムの実行プロセスがコールバック関数の層に分散されます。コールバック地獄を解決するには、主に Promise とコルーチンの 2 つの方法があります。コルーチンは同期方式でコードを作成できるため、高パフォーマンスのネットワーク プログラミング (IO 集中型) で推奨されます。

PHP でのコルーチン プログラミングを振り返ってみましょう。 PHP では、ジェネレーターに基づいてコルーチン プログラミングが実装されるため、RecoilPHPAmp などのコルーチン フレームワークを使用することをお勧めします。これらのフレームワークにはすでにスケジューラーが記述されており、その上でジェネレーター関数を直接開発すると、カーネルが自動的に実行をスケジュールします (関数をコルーチン モードで実行するようにスケジュールしたい場合は、単に 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 は非同期であり、ノンブロッキングです。

ジェネレーターと Promise を使用すると、

await に似たコルーチン プログラミングを実装できます。関連するコードは Github に多数あるため、この記事では説明しません。

関連する推奨事項: PHP の

$_SERVER 詳細な紹介

PHP の Output_buffering の詳細な紹介、outputbuffering_PHP チュートリアル

以上がphp のコルーチンの詳細な紹介 (コード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。