您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎么在PHP中實現生成器和協程,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
迭代和迭代器
在理解本文大多數概念前,有必要知道迭代和迭代器。事實上,迭代大家都知道是什么,可是我不知道(真的,在此之前對這個概念沒有系統了解)。迭代是指反復執行一個過程,每執行一次叫做一次迭代。實際上我們經常做這種事情,比如:
<?php $mapping = [ 'red' => '#FF0000', 'green' => '#00FF00', 'blue' => '#0000FF' ]; foreach ($mapping as $key => $value) { printf("key: %d - value: %s\n", $key, $value); }
我們可以看到通過 foreach
對數組遍歷并迭代輸出其內容。在這一環節中,我們需要關注的重點是數組。雖然我們迭代的過程是 foreach
語句中的代碼塊,但實際上數組 $mapping
在每一次迭代中發生了變化,意味著數組內部也存在著一次迭代。如果我們把數組看做一個對象,foreach 實際上在每一次迭代過程都會調用該對象的一個方法,讓數組在自己內部進行一次變動(迭代),隨后通過另一個方法取出當前數組對象的鍵和值。這樣一個可通過外部遍歷其內部數據的對象就是一個迭代器對象,其遵循的統一的訪問接口就是迭代器接口(Iterator
)。
PHP 提供了一個統一的迭代器接口。關于迭代器 PHP 官方文檔有更為詳細的描述,建議去了解。
interface Iterator extends Traversable { /** * 獲取當前內部標量指向的元素的數據 */ public mixed current ( void ) /** * 獲取當前標量 */ public scalar key ( void ) /** * 移動到下一個標量 */ public void next ( void ) /** * 重置標量 */ public void rewind ( void ) /** * 檢查當前標量是否有效 */ public boolean valid ( void ) }
我們來給出一個實例,去實現一個簡單的迭代器:
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 遍歷來看看這個迭代器的效果:
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
至此我們看到了一個迭代器的實現。一些人在了解這一特性會很激動的將其應用在實際項目中,但有些則疑惑這有什么卵用呢?迭代器只是將一個普通對象變成了一個可被遍歷的對象,這在有些時候,如一個對象 StudentsContact,這個對象是用于處理學生聯系方式的,通過 addStudent 方法注冊學生,通過 getAllStudent 獲取全部注冊的學生聯系方式數組。我們以往遍歷是通過 StudentsContact::getAllStudent() 獲取一個數組然后遍歷該數組,但是現在有了迭代器,只要這個類繼承這個接口,就可以直接遍歷該對象獲取學生數組,并且可以在獲取之前在類的內部就對輸出的數據做好處理工作。
當然用處遠不止這么點,但在這里就不過多糾結。有一個在此基礎上更為強大的東西,生成器。
生成器,Generator
雖然迭代器僅需繼承接口即可實現,但依舊很麻煩,我們畢竟需要定義一個類并實現該接口所有方法,這十分繁瑣。在一些情景下我們需要更簡潔的辦法。生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 接口的方式,性能開銷和復雜性大大降低。
PHP 官方文檔這樣說的:
生成器允許你在 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('hello'); $printer->send('world');
上述例子輸出內容為:
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('hello'); printf("%d\n", $printer->current()); $printer->send('world'); printf("%d\n", $printer->current());
輸出內容如下:
1
receive: hello
2
receive: world
3
current
方法是迭代器( Iterator
)接口必要的方法,foreach
語句每一次迭代都會通過其獲取當前值,而后調用迭代器的 next
方法。我們為了使程序不會無限執行,手動調用 current 方法獲取值。
上述例子已經足以表示 yield 在那一個位置作為雙向傳輸的 工具,已具備實現協程的條件。
協程
這一部分我不打算長篇大論,本文開頭已經給出了鳥哥博客中更為完善的文章,本文的目的是出于補充對 Generator 的細節。
我們要知道,對于單核處理器,多任務的執行原理是讓每一個任務執行一段時間,然后中斷、讓另一個任務執行然后在中斷后執行下一個,如此反復。由于其執行切換速度很快,讓外部認為多個任務實際上是 “并行” 的。
鳥哥那篇文章這么說道:
多任務協作這個術語中的 “協作” 很好的說明了如何進行這種切換的:它要求當前正在運行的任務自動把控制傳回給調度器,這樣就可以運行其他任務了。這與 “搶占” 多任務相反, 搶占多任務是這樣的:調度器可以中斷運行了一段時間的任務, 不管它喜歡還是不喜歡。協作多任務在 Windows 的早期版本 (windows95) 和 Mac OS 中有使用, 不過它們后來都切換到使用搶先多任務了。理由相當明確:如果你依靠程序自動交出控制的話,那么一些惡意的程序將很容易占用整個CPU,不與其他任務共享。
php,一個嵌套的縮寫名稱,是英文超級文本預處理語言(PHP:Hypertext Preprocessor)的縮寫。PHP 是一種 HTML 內嵌式的語言,PHP與微軟的ASP頗有幾分相似,都是一種在服務器端執行的嵌入HTML文檔的腳本語言,語言的風格有類似于C語言,現在被很多的網站編程人員廣泛的運用。
以上就是怎么在PHP中實現生成器和協程,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。