溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在PHP中實(shí)現(xiàn)生成器和協(xié)程

發(fā)布時(shí)間:2021-05-24 16:16:28 來源:億速云 閱讀:103 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)怎么在PHP中實(shí)現(xiàn)生成器和協(xié)程,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

迭代和迭代器

在理解本文大多數(shù)概念前,有必要知道迭代和迭代器。事實(shí)上,迭代大家都知道是什么,可是我不知道(真的,在此之前對(duì)這個(gè)概念沒有系統(tǒng)了解)。迭代是指反復(fù)執(zhí)行一個(gè)過程,每執(zhí)行一次叫做一次迭代。實(shí)際上我們經(jīng)常做這種事情,比如:

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

我們可以看到通過 foreach 對(duì)數(shù)組遍歷并迭代輸出其內(nèi)容。在這一環(huán)節(jié)中,我們需要關(guān)注的重點(diǎn)是數(shù)組。雖然我們迭代的過程是 foreach 語(yǔ)句中的代碼塊,但實(shí)際上數(shù)組 $mapping 在每一次迭代中發(fā)生了變化,意味著數(shù)組內(nèi)部也存在著一次迭代。如果我們把數(shù)組看做一個(gè)對(duì)象,foreach 實(shí)際上在每一次迭代過程都會(huì)調(diào)用該對(duì)象的一個(gè)方法,讓數(shù)組在自己內(nèi)部進(jìn)行一次變動(dòng)(迭代),隨后通過另一個(gè)方法取出當(dāng)前數(shù)組對(duì)象的鍵和值。這樣一個(gè)可通過外部遍歷其內(nèi)部數(shù)據(jù)的對(duì)象就是一個(gè)迭代器對(duì)象,其遵循的統(tǒng)一的訪問接口就是迭代器接口(Iterator)。

PHP 提供了一個(gè)統(tǒng)一的迭代器接口。關(guān)于迭代器 PHP 官方文檔有更為詳細(xì)的描述,建議去了解。

interface Iterator extends Traversable
{
  /**
   * 獲取當(dāng)前內(nèi)部標(biāo)量指向的元素的數(shù)據(jù)
   */
  public mixed current ( void )
  /**
   * 獲取當(dāng)前標(biāo)量
   */
  public scalar key ( void )
  /**
   * 移動(dòng)到下一個(gè)標(biāo)量
   */
  public void next ( void )
  /**
   * 重置標(biāo)量
   */
  public void rewind ( void )
  /**
   * 檢查當(dāng)前標(biāo)量是否有效
   */
  public boolean valid ( void )
}

我們來給出一個(gè)實(shí)例,去實(shí)現(xiàn)一個(gè)簡(jiǎn)單的迭代器:

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;
  }
}

通過 foreach 遍歷來看看這個(gè)迭代器的效果:

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

輸出:

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

至此我們看到了一個(gè)迭代器的實(shí)現(xiàn)。一些人在了解這一特性會(huì)很激動(dòng)的將其應(yīng)用在實(shí)際項(xiàng)目中,但有些則疑惑這有什么卵用呢?迭代器只是將一個(gè)普通對(duì)象變成了一個(gè)可被遍歷的對(duì)象,這在有些時(shí)候,如一個(gè)對(duì)象 StudentsContact,這個(gè)對(duì)象是用于處理學(xué)生聯(lián)系方式的,通過 addStudent 方法注冊(cè)學(xué)生,通過 getAllStudent 獲取全部注冊(cè)的學(xué)生聯(lián)系方式數(shù)組。我們以往遍歷是通過 StudentsContact::getAllStudent() 獲取一個(gè)數(shù)組然后遍歷該數(shù)組,但是現(xiàn)在有了迭代器,只要這個(gè)類繼承這個(gè)接口,就可以直接遍歷該對(duì)象獲取學(xué)生數(shù)組,并且可以在獲取之前在類的內(nèi)部就對(duì)輸出的數(shù)據(jù)做好處理工作。

當(dāng)然用處遠(yuǎn)不止這么點(diǎn),但在這里就不過多糾結(jié)。有一個(gè)在此基礎(chǔ)上更為強(qiáng)大的東西,生成器。

生成器,Generator

雖然迭代器僅需繼承接口即可實(shí)現(xiàn),但依舊很麻煩,我們畢竟需要定義一個(gè)類并實(shí)現(xiàn)該接口所有方法,這十分繁瑣。在一些情景下我們需要更簡(jiǎn)潔的辦法。生成器提供了一種更容易的方法來實(shí)現(xiàn)簡(jiǎn)單的對(duì)象迭代,相比較定義類實(shí)現(xiàn) Iterator 接口的方式,性能開銷和復(fù)雜性大大降低。

PHP 官方文檔這樣說的:

生成器允許你在 foreach 代碼塊中寫代碼來迭代一組數(shù)據(jù)而不需要在內(nèi)存中創(chuàng)建一個(gè)數(shù)組, 那會(huì)使你的內(nèi)存達(dá)到上限,或者會(huì)占據(jù)可觀的處理時(shí)間。相反,你可以寫一個(gè)生成器函數(shù),就像一個(gè)普通的自定義函數(shù)一樣, 和普通函數(shù)只返回一次不同的是, 生成器可以根據(jù)需要 yield 多次,以便生成需要迭代的值。

一個(gè)簡(jiǎn)單的例子就是使用生成器來重新實(shí)現(xiàn) range() 函數(shù)。 標(biāo)準(zhǔn)的 range() 函數(shù)需要在內(nèi)存中生成一個(gè)數(shù)組包含每一個(gè)在它范圍內(nèi)的值,然后返回該數(shù)組, 結(jié)果就是會(huì)產(chǎn)生多個(gè)很大的數(shù)組。 比如,調(diào)用 range(0, 1000000) 將導(dǎo)致內(nèi)存占用超過 100 MB。

做為一種替代方法, 我們可以實(shí)現(xiàn)一個(gè) xrange() 生成器, 只需要足夠的內(nèi)存來創(chuàng)建 Iterator 對(duì)象并在內(nèi)部跟蹤生成器的當(dāng)前狀態(tài),這樣只需要不到1K字節(jié)的內(nèi)存。

官方文檔給了上文對(duì)應(yīng)的例子,我們?cè)诖撕?jiǎn)化了一下:

function xrange($start, $limit, $step = 1) {
  for ($i = $start; $i <= $limit; $i += $step) {
    yield $i + 1 => $i; // 關(guān)鍵字 yield 表明這是一個(gè) generator
  }
}
// 我們可以這樣調(diào)用
foreach (xrange(0, 10, 2) as $key => $value) {
  printf("%d %d\n", $key, $value);
}

可能你已經(jīng)發(fā)現(xiàn)了,這個(gè)例子的輸出和我們前面在說迭代器的時(shí)候那個(gè)例子結(jié)果一樣。實(shí)際上生成器生成的正是一個(gè)迭代器對(duì)象實(shí)例,該迭代器對(duì)象繼承了 Iterator 接口,同時(shí)也包含了生成器對(duì)象自有的接口,具體可以參考 Generator 類的定義。

當(dāng)一個(gè)生成器被調(diào)用的時(shí)候,它返回一個(gè)可以被遍歷的對(duì)象.當(dāng)你遍歷這個(gè)對(duì)象的時(shí)候(例如通過一個(gè)foreach循環(huán)),PHP 將會(huì)在每次需要值的時(shí)候調(diào)用生成器函數(shù),并在產(chǎn)生一個(gè)值之后保存生成器的狀態(tài),這樣它就可以在需要產(chǎn)生下一個(gè)值的時(shí)候恢復(fù)調(diào)用狀態(tài)。

一旦不再需要產(chǎn)生更多的值,生成器函數(shù)可以簡(jiǎn)單退出,而調(diào)用生成器的代碼還可以繼續(xù)執(zhí)行,就像一個(gè)數(shù)組已經(jīng)被遍歷完了。

我們需要注意的關(guān)鍵是 yield,這是生成器的關(guān)鍵。我們通過上面例子,可以看得出,yield 會(huì)將當(dāng)前一個(gè)值傳遞給 foreach,換句話說,foreach 每一次迭代過程都會(huì)從 yield 處取一個(gè)值,直到整個(gè)遍歷過程不再存在 yield 為止的時(shí)候,遍歷結(jié)束。

我們也可以發(fā)現(xiàn),yield 和 return 都會(huì)返回值,但區(qū)別在于一個(gè) return 是返回既定結(jié)果,一次返回完畢就不再返回新的結(jié)果,而 yield 是不斷產(chǎn)出直到無法產(chǎn)出為止。

實(shí)際上存在 yield 的函數(shù)返回值返回的是一個(gè) Generator 對(duì)象(這個(gè)對(duì)象不能手動(dòng)通過 new 實(shí)例化),該對(duì)象實(shí)現(xiàn)了 Iterator 接口。那么 Generator 自身有什么獨(dú)特之處?繼續(xù)看:

yield

字面上解釋,yield 代表著讓位、讓行。正是這個(gè)讓行使得通過 yield 實(shí)現(xiàn)協(xié)程變得可能。

生成器函數(shù)的核心是 yield 關(guān)鍵字。它最簡(jiǎn)單的調(diào)用形式看起來像一個(gè) return 申明,不同之處在于普通 return 會(huì)返回值并終止函數(shù)的執(zhí)行,而 yield 會(huì)返回一個(gè)值給循環(huán)調(diào)用此生成器的代碼并且只是暫停執(zhí)行生成器函數(shù)。

yield 和 return 的區(qū)別,前者是暫停當(dāng)前過程的執(zhí)行并返回值,而后者是中斷當(dāng)前過程并返回值。暫停當(dāng)前過程,意味著將處理權(quán)轉(zhuǎn)交由上一級(jí)繼續(xù)進(jìn)行,直至上一級(jí)再次調(diào)用被暫停的過程,該過程則會(huì)從上一次暫停的位置繼續(xù)執(zhí)行。這像是什么呢?如果讀者在讀本篇文章之前已經(jīng)在鳥哥的文章中粗略看過,應(yīng)該知道這很像是一個(gè)操作系統(tǒng)的進(jìn)程調(diào)度管理,多個(gè)進(jìn)程在一個(gè) CPU 核心上執(zhí)行,在系統(tǒng)調(diào)度下每一個(gè)進(jìn)程執(zhí)行一段指令就被暫停,切換到下一個(gè)進(jìn)程,這樣看起來就像是同時(shí)在執(zhí)行多個(gè)任務(wù)。

但僅僅是如此還遠(yuǎn)遠(yuǎn)不夠,yield 更重要的特性是除了可以返回一個(gè)值以外,還能夠接收一個(gè)值!

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

上述例子輸出內(nèi)容為:

receive: hello
receive: world

參考 PHP 官方中文文檔:生成器 對(duì)象 我們可以得知 Generator 對(duì)象除了實(shí)現(xiàn) Iterator 接口中的必要方法以外,還有一個(gè) send 方法,這個(gè)方法就是向 yield 語(yǔ)句處傳遞一個(gè)值,同時(shí)從 yied 語(yǔ)句處繼續(xù)執(zhí)行,直至再次遇到 yield 后控制權(quán)回到外部。

我們通過之前也了解了一個(gè)問題,yield 可以在其位置中斷并返回一個(gè)值,那么能不能同時(shí)進(jìn)行 接收返回 呢?當(dāng)然,這可是實(shí)現(xiàn)協(xié)程的根本。我們對(duì)上述代碼做出修改:

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

輸出內(nèi)容如下:

1
receive: hello
2
receive: world
3

current 方法是迭代器( Iterator )接口必要的方法,foreach 語(yǔ)句每一次迭代都會(huì)通過其獲取當(dāng)前值,而后調(diào)用迭代器的 next 方法。我們?yōu)榱耸钩绦虿粫?huì)無限執(zhí)行,手動(dòng)調(diào)用 current 方法獲取值。

上述例子已經(jīng)足以表示 yield 在那一個(gè)位置作為雙向傳輸?shù)?工具,已具備實(shí)現(xiàn)協(xié)程的條件。

協(xié)程

這一部分我不打算長(zhǎng)篇大論,本文開頭已經(jīng)給出了鳥哥博客中更為完善的文章,本文的目的是出于補(bǔ)充對(duì) Generator 的細(xì)節(jié)。

我們要知道,對(duì)于單核處理器,多任務(wù)的執(zhí)行原理是讓每一個(gè)任務(wù)執(zhí)行一段時(shí)間,然后中斷、讓另一個(gè)任務(wù)執(zhí)行然后在中斷后執(zhí)行下一個(gè),如此反復(fù)。由于其執(zhí)行切換速度很快,讓外部認(rèn)為多個(gè)任務(wù)實(shí)際上是 “并行” 的。

鳥哥那篇文章這么說道:

多任務(wù)協(xié)作這個(gè)術(shù)語(yǔ)中的 “協(xié)作” 很好的說明了如何進(jìn)行這種切換的:它要求當(dāng)前正在運(yùn)行的任務(wù)自動(dòng)把控制傳回給調(diào)度器,這樣就可以運(yùn)行其他任務(wù)了。這與 “搶占” 多任務(wù)相反, 搶占多任務(wù)是這樣的:調(diào)度器可以中斷運(yùn)行了一段時(shí)間的任務(wù), 不管它喜歡還是不喜歡。協(xié)作多任務(wù)在 Windows 的早期版本 (windows95) 和 Mac OS 中有使用, 不過它們后來都切換到使用搶先多任務(wù)了。理由相當(dāng)明確:如果你依靠程序自動(dòng)交出控制的話,那么一些惡意的程序?qū)⒑苋菀渍加谜麄€(gè)CPU,不與其他任務(wù)共享。

php是什么語(yǔ)言

php,一個(gè)嵌套的縮寫名稱,是英文超級(jí)文本預(yù)處理語(yǔ)言(PHP:Hypertext Preprocessor)的縮寫。PHP 是一種 HTML 內(nèi)嵌式的語(yǔ)言,PHP與微軟的ASP頗有幾分相似,都是一種在服務(wù)器端執(zhí)行的嵌入HTML文檔的腳本語(yǔ)言,語(yǔ)言的風(fēng)格有類似于C語(yǔ)言,現(xiàn)在被很多的網(wǎng)站編程人員廣泛的運(yùn)用。

以上就是怎么在PHP中實(shí)現(xiàn)生成器和協(xié)程,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI