Heim  >  Artikel  >  Backend-Entwicklung  >  Wie werden Generatoren und Coroutinen in PHP implementiert?

Wie werden Generatoren und Coroutinen in PHP implementiert?

不言
不言Original
2018-07-21 13:11:001264Durchsuche

Dieser Artikel zeigt Ihnen, wie Generatoren und Coroutinen in PHP implementiert werden. Der Inhalt ist sehr gut. Ich hoffe, er kann allen helfen.

Lass uns zuerst etwas Unsinn reden

Seit PHP 5.5 haben viele neue Features PHP noch einmal zu neuem Glanz verholfen, obwohl in diesem Artikel darauf hingewiesen wird Dies geschah einige Zeit nach der Veröffentlichung von PHP 7 Alpha 2, aber zu diesem Zeitpunkt wurde das Land noch von PHP 5.3 dominiert. Ich denke jedoch, dass neue Funktionen früher oder später wichtiger werden, da die alten Versionen nach und nach verschwinden, insbesondere nach der Veröffentlichung der offiziellen Version von PHP 7. Daher besteht der Zweck dieses Artikels darin, einigen PHPern zu helfen, etwas zu verstehen, was sie noch nie verstanden haben schon mal etwas verstanden. Daher habe ich vor, diesen Artikel als Beginn einer Artikelserie zu verwenden, um die PHP-Kenntnisse im Blog zu vervollständigen.

Tatsächlich hatte ich vor dem Schreiben dieses Artikels kein relativ intuitives Verständnis des Generators und der Coroutine-Implementierung von PHP, die auf dieser Funktion basiert. Hauptsächlich, weil mein persönliches Niveau nicht sehr hoch ist und ich ein typischer bin Neuling. Die Tür zu PHPer. Nachdem ich vor einiger Zeit die Erklärung von Coroutinen im Blog von Laruence gelesen hatte (Referenzlink: „Using Coroutines to Implement Cooperative Multitasking in PHP“), konzentrierte ich mich auf der Grundlage meines persönlichen Verständnisses dieses Artikels auf diejenigen, die schwieriger zu verstehen sind Dieses Konzept (einschließlich meiner persönlichen Schwierigkeiten, dieses Konzept zu verstehen) wird auf populärere Weise erklärt. Da ich gerade erst angefangen habe, dieses Konzept zu lernen, ist es natürlich unvermeidlich, dass ich einige unangemessene Fehler mache. Wenn Sie es sehen, können Sie mich gerne aufklären.

Alles beginnt mit Iterator und Generator

Um das Verständnis für neue Entwickler zu erleichtern, befasst sich die Hälfte dieses Artikels mit dem Iterator-Schnittstelle ( Iterator) und Generator-Klassen. Wenn Sie dies bereits verstehen, können Sie es direkt überspringen.

Iteration und Iteratoren

Bevor Sie die meisten Konzepte in diesem Artikel verstehen, müssen Sie Iteration und Iteratoren kennen. Tatsächlich weiß jeder, was Iteration ist, aber ich weiß es nicht (wirklich, ich hatte vorher kein systematisches Verständnis dieses Konzepts). Unter Iteration versteht man die wiederholte Ausführung eines Prozesses. Jede Ausführung wird als Iteration bezeichnet. Tatsächlich machen wir so etwas oft, wie zum Beispiel:

<?php
$mapping = [
  &#39;red&#39;  => &#39;#FF0000&#39;,
  &#39;green&#39; => &#39;#00FF00&#39;,
  &#39;blue&#39; => &#39;#0000FF&#39;
];
foreach ($mapping as $key => $value) {
  printf("key: %d - value: %s\n", $key, $value);
}

Wir können sehen, dass foreach das Array durchläuft und seinen Inhalt iterativ ausgibt. In diesem Abschnitt müssen wir uns auf Arrays konzentrieren. Obwohl unser Iterationsprozess der Codeblock in der foreach-Anweisung ist, ändert sich das Array $mapping tatsächlich bei jeder Iteration, was bedeutet, dass es auch eine Iteration innerhalb des Arrays gibt. Wenn wir das Array als Objekt betrachten, ruft foreach während jedes Iterationsprozesses tatsächlich eine Methode des Objekts auf, sodass sich das Array intern ändern (iterieren) kann, und ruft dann die Schlüssel und Werte des aktuellen Array-Objekts über eine andere Methode ab. . Ein solches Objekt, dessen interne Daten extern durchlaufen werden können, ist ein Iteratorobjekt, und die darauf folgende einheitliche Zugriffsschnittstelle ist die Iteratorschnittstelle (Iterator).

PHP bietet eine einheitliche Iteratorschnittstelle. Die offizielle PHP-Dokumentation zu Iteratoren enthält eine detailliertere Beschreibung und es wird empfohlen, diese zu lesen.

interface Iterator extends Traversable
{
  /**
   * 获取当前内部标量指向的元素的数据
   */
  public mixed current ( void )
  /**
   * 获取当前标量
   */
  public scalar key ( void )
  /**
   * 移动到下一个标量
   */
  public void next ( void )
  /**
   * 重置标量
   */
  public void rewind ( void )
  /**
   * 检查当前标量是否有效
   */
  public boolean valid ( void )
}

Geben wir ein Beispiel für die Implementierung eines einfachen Iterators:

class Xrange implements Iterator
{
  protected $start;
  protected $limit;
  protected $step;
  protected $i;
  public function __construct($start, $limit, $step = 0)
  {
    $this->start = $start;
    $this->limit = $limit;
    $this->step = $step;
  }
  public function rewind()
  {
    $this->i = $this->start;
  }
  public function next()
  {
    $this->i += $this->step;
  }
  public function current()
  {
    return $this->i;
  }
  public function key()
  {
    return $this->i + 1;
  }
  public function valid()
  {
    return $this->i <= $this->limit;
  }
}

Sehen wir uns die Wirkung dieses Iterators durch foreach-Traversal an:

foreach (new Xrange(0, 10, 2) as $key => $value) {
  printf("%d %d\n", $key, $value);
}

Ausgabe:

1 0
3 2
5 4
7 6
9 8
11 10

Bisher haben wir eine Iterator-Implementierung gesehen. Einige Leute werden sehr begeistert sein, diese Funktion in tatsächlichen Projekten anzuwenden, nachdem sie davon erfahren haben, aber andere sind verwirrt darüber, welchen Nutzen sie hat? Der Iterator verwandelt einfach ein gewöhnliches Objekt in ein Objekt, das durchlaufen werden kann. In einigen Fällen wird dieses Objekt verwendet, um Studentenkontaktinformationen über die Methode addStudent zu verarbeiten und alle registrierten Studenten über getAllStudent abzurufen. Eine Reihe von Kontaktinformationen für Studierende. In der Vergangenheit haben wir StudentsContact::getAllStudent() verwendet, um ein Array abzurufen und dann das Array zu durchlaufen. Mit dem Iterator können wir jetzt, solange die Klasse diese Schnittstelle erbt, das Objekt direkt durchlaufen, um das Student-Array zu erhalten Wir können es der Klasse hinzufügen, bevor wir es erhalten. Die Ausgabedaten werden intern verarbeitet.

Natürlich hat es weitaus mehr Verwendungsmöglichkeiten, aber ich werde mich hier nicht zu sehr darauf einlassen. Auf dieser Basis gibt es etwas Stärkeres: Generatoren.

Generator, Generator

Obwohl Iteratoren durch einfaches Erben der Schnittstelle implementiert werden können, ist dies immer noch sehr mühsam. Schließlich müssen wir eine Klasse definieren und alles implementieren Methoden der Schnittstelle, was sehr umständlich ist. In manchen Situationen brauchen wir einen einfacheren Ansatz. Generatoren bieten eine einfachere Möglichkeit, eine einfache Objektiteration zu implementieren, mit deutlich geringerem Leistungsaufwand und geringerer Komplexität im Vergleich zur Definition einer Klasse zur Implementierung der Iterator-Schnittstelle.

Die offizielle Dokumentation von PHP besagt Folgendes:

生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

一个简单的例子就是使用生成器来重新实现 range() 函数。 标准的 range() 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。

做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到1K字节的内存。

官方文档给了上文对应的例子,我们在此简化了一下:

function xrange($start, $limit, $step = 1) {
  for ($i = $start; $i <= $limit; $i += $step) {
    yield $i + 1 => $i; // 关键字 yield 表明这是一个 generator
  }
}
// 我们可以这样调用
foreach (xrange(0, 10, 2) as $key => $value) {
  printf("%d %d\n", $key, $value);
}

可能你已经发现了,这个例子的输出和我们前面在说迭代器的时候那个例子结果一样。实际上生成器生成的正是一个迭代器对象实例,该迭代器对象继承了 Iterator 接口,同时也包含了生成器对象自有的接口,具体可以参考 Generator 类的定义。

当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。

一旦不再需要产生更多的值,生成器函数可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。

我们需要注意的关键是 yield,这是生成器的关键。我们通过上面例子,可以看得出,yield 会将当前一个值传递给 foreach,换句话说,foreach 每一次迭代过程都会从 yield 处取一个值,直到整个遍历过程不再存在 yield 为止的时候,遍历结束。

我们也可以发现,yield 和 return 都会返回值,但区别在于一个 return 是返回既定结果,一次返回完毕就不再返回新的结果,而 yield 是不断产出直到无法产出为止。

实际上存在 yield 的函数返回值返回的是一个 Generator 对象(这个对象不能手动通过 new 实例化),该对象实现了 Iterator 接口。那么 Generator 自身有什么独特之处?继续看:

yield

字面上解释,yield 代表着让位、让行。正是这个让行使得通过 yield 实现协程变得可能。

生成器函数的核心是 yield 关键字。它最简单的调用形式看起来像一个 return 申明,不同之处在于普通 return 会返回值并终止函数的执行,而 yield 会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

yield 和 return 的区别,前者是暂停当前过程的执行并返回值,而后者是中断当前过程并返回值。暂停当前过程,意味着将处理权转交由上一级继续进行,直至上一级再次调用被暂停的过程,该过程则会从上一次暂停的位置继续执行。这像是什么呢?如果读者在读本篇文章之前已经在鸟哥的文章中粗略看过,应该知道这很像是一个操作系统的进程调度管理,多个进程在一个 CPU 核心上执行,在系统调度下每一个进程执行一段指令就被暂停,切换到下一个进程,这样看起来就像是同时在执行多个任务。

但仅仅是如此还远远不够,yield 更重要的特性是除了可以返回一个值以外,还能够接收一个值!

function printer()
{
  while (true) {
    printf("receive: %s\n", yield);
  }
}
$printer = printer();
$printer->send(&#39;hello&#39;);
$printer->send(&#39;world&#39;);

上述例子输出内容为:

receive: hello
receive: world

参考 PHP 官方中文文档:生成器 对象 我们可以得知 Generator 对象除了实现 Iterator 接口中的必要方法以外,还有一个 send 方法,这个方法就是向 yield 语句处传递一个值,同时从 yied 语句处继续执行,直至再次遇到 yield 后控制权回到外部。

我们通过之前也了解了一个问题,yield 可以在其位置中断并返回一个值,那么能不能同时进行 接收返回 呢?当然,这可是实现协程的根本。我们对上述代码做出修改:

<?php
function printer()
{
  $i = 0;
  while (true) {
    printf("receive: %s\n", (yield ++$i));
  }
}
$printer = printer();
printf("%d\n", $printer->current());
$printer->send(&#39;hello&#39;);
printf("%d\n", $printer->current());
$printer->send(&#39;world&#39;);
printf("%d\n", $printer->current());

输出内容如下:

1
receive: hello
2
receive: world
3

current 方法是迭代器( Iterator )接口必要的方法,foreach 语句每一次迭代都会通过其获取当前值,而后调用迭代器的 next 方法。我们为了使程序不会无限执行,手动调用 current 方法获取值。

上述例子已经足以表示 yield 在那一个位置作为双向传输的 工具,已具备实现协程的条件。

协程

这一部分我不打算长篇大论,本文开头已经给出了鸟哥博客中更为完善的文章,本文的目的是出于补充对 Generator 的细节。

我们要知道,对于单核处理器,多任务的执行原理是让每一个任务执行一段时间,然后中断、让另一个任务执行然后在中断后执行下一个,如此反复。由于其执行切换速度很快,让外部认为多个任务实际上是 “并行” 的。

鸟哥那篇文章这么说道:

多任务协作这个术语中的 “协作” 很好的说明了如何进行这种切换的:它要求当前正在运行的任务自动把控制传回给调度器,这样就可以运行其他任务了。这与 “抢占” 多任务相反, 抢占多任务是这样的:调度器可以中断运行了一段时间的任务, 不管它喜欢还是不喜欢。协作多任务在 Windows 的早期版本 (windows95) 和 Mac OS 中有使用, 不过它们后来都切换到使用抢先多任务了。理由相当明确:如果你依靠程序自动交出控制的话,那么一些恶意的程序将很容易占用整个CPU,不与其他任务共享。

我们结合之前的例子,可以发现,yield 作为可以让一段任务自身中断,然后回到外部继续执行。利用这个特性可以实现多任务调度的功能,配合 yield 的双向通讯功能,以实现任务和调度器之间进行通信。

这样的功能对于读写和操作 Stream 资源时尤为重要,我们可以极大的提高程序对于并发流资源的处理能力,比如实现 tcp server。

总结

PHP 自 5.4 到如今愈发稳定的 PHP 7,可以看到许多的新特性令这门语言愈发强大和完善,逐渐从纯粹的 Web 语言变得有着更为广泛的适用面,作为一枚 PHPer 的确不应当止步不前,我们依然有很多的东西需要不断学习和加强。

虽然 “PHP 是世界上最好的语言” 这句话只是个调侃,但不可否认 PHP 即使不是最好,但也在努力变好的事实,对吧?

相关推荐:

PHP7新特性中抽象语法树(AST)的一些介绍

利用PHPExcel如何读取表格中内容

Das obige ist der detaillierte Inhalt vonWie werden Generatoren und Coroutinen in PHP implementiert?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn