雖然以前就接觸了PHP的yield關鍵字和與之對應的生成器,可是一直沒有場景去使用它,就一直沒有對它上心的研究。不過公司的框架是基於php的協程
實現,以爲有必要深刻的瞅瞅了。php
因爲以前對於生成器
接觸很少,後來也是在看了鳥哥的介紹在PHP中使用協程實現多任務調度纔有所瞭解。下面也只是說說個人理解。html
在瞭解生成器以前咱們先來看一下迭代器和迭代。迭代是指反覆執行一個過程,每執行一次叫作迭代一次。好比普通的遍歷即是迭代:數組
$arr = [1, 2, 3, 4, 5]; foreach($arr as $key => $value) { echo $key . ' => ' . $value . "\n"; }
咱們能夠看到經過foreach
對數組遍歷並迭代輸出其內容。在foreach
內部,每次迭代都會將當前的元素的值賦給$value
並將數組的指針移動指向下一個元素爲下一次迭代坐準備,從而實現順序遍歷。像這樣可以讓外部的函數迭代本身內部數據的接口就是迭代器接口
,對應的那個被迭代的本身就是迭代器對象
。框架
PHP提供了統一的迭代器接口:函數
Iterator extends Traversable { // 返回當前的元素 abstract public mixed current(void) // 返回當前元素的鍵 abstract public scalar key(void) // 向下移動到下一個元素 abstract public void next(void) // 返回到迭代器的第一個元素 abstract public void rewind(void) // 檢查當前位置是否有效 abstract public boolean valid(void) }
經過實現Iterator接口
,咱們能夠自行的決定如何遍歷對象。好比經過實現Iterator接口
咱們能夠觀察迭代器的調用順序。工具
class MyIterator implements Iterator { private $position = 0; private $arr = [ 'first', 'second', 'third', ]; public function __construct() { $this->position = 0; } public function rewind() { var_dump(__METHOD__); $this->position = 0; } public function current() { var_dump(__METHOD__); return $this->arr[$this->position]; } public function key() { var_dump(__METHOD__); return $this->position; } public function next() { var_dump(__METHOD__); ++$this->position; } public function valid() { var_dump(__METHOD__); return isset($this->arr[$this->position]); } } $it = new MyIterator(); foreach($it as $key => $value) { echo "\n"; var_dump($key, $value); }
經過這個例子可以清楚的看到了foreach
循環中調用的順序。從例子也能看出經過迭代器可以將一個普通的對象轉化爲一個可被遍歷的對象。這在有些時候,可以將一個普通的UsersInfo
對象轉化爲一個能夠遍歷的對象,那麼就不須要經過UsersInfo::getAllUser()
獲取一個數組而後遍歷數組,並且還能夠在對象中對數據進行預處理。性能
相比較迭代器,生成器提供了一種更容易的方法來實現簡單的對象迭代
,性能開銷和複雜性都大大下降。this
一個生成器函數看起來像一個普通的函數,不一樣的是普通函數返回一個值,而一個生成能夠yield
生成許多它所須要的值,而且每一次的生成返回值只是暫停當前的執行狀態,當下次調用生成器函數時,PHP會從上次暫停的狀態繼續執行下去。.net
咱們在使用生成器的時候能夠像關聯數組那樣指定一個鍵名對應生成的值。以下生成一個鍵值對與定義一個關聯數組類似。scala
function xrange($start, $limit, $step = 1) { for ($i = $start, $j = 0; $i <= $limit; $i += $step, $j++) { // 給予鍵值 yield $j => $i; } } $xrange = xrange(1, 10, 2); foreach ($xrange as $key => $value) { echo $key . ' => ' . $value . "\n"; }
更多的生成器語法能夠參見生成器語法
實際上生成器函數返回的是一個Generator
對象,這個對象不能經過new實例化,而且實現了Iterator
接口。
Generator implements Iterator { public mixed current(void) public mixed key(void) public void next(void) public void rewind(void) // 向生成器傳入一個值 public mixed send(mixed $value) public void throw(Exception $exception) public bool valid(void) // 序列化回調 public void __wakeup(void) }
能夠看到出了實現Iterator
的接口以外Generator
還添加了send
方法,用來向生成器傳入一個值,而且當作yield
表達式的結果,而後繼續執行生成器,直到遇到下一個yield
後會再次停住。
function printer() { while(true) { echo 'receive: ' . yield . "\n"; } } $printer = printer(); $printer->send('Hello'); $printer->send('world');
以上的例子會輸出:
receive: Hello receive: world
在上面的例子中,通過第一個send()
方法,yield
表達式的值變爲Hello
,以後執行echo
語句,輸出第一條結果receive: Hello
,輸出完畢後繼續執行到第二個yield
處,只不過當前的語句沒有執行到底,不會執行輸出。若是將例子改改就可以看出來yield
的繼續執行到哪裏。
function printer() { $i = 1; while(true) { echo 'this is the yield ' . $i . "\n"; echo 'receive: ' . yield . "\n"; $i++; } } $printer = printer(); $printer->send('Hello'); $printer->send('world');
此次的輸出便會變爲:
this is the yield 1 receive: hello this is the yield 2 receive: world this is the yield 3
這邊能夠清楚的看出send
以後的繼續執行到第二個yield
處,以前的代碼照常執行。
咱們再對例子進行適當的修改:
function printer() { $i = 1; while(true) { echo 'this is the yield ' . (yield $i) . "\n"; $i++; } } $printer = printer(); var_dump($printer->send('first')); var_dump($printer->send('second'));
執行一下會發現結果爲:
this is the yield first int(2) this is the yield second int(3)
讓咱們來看一下,是否是發現了問題,跑出來的結果不是從1開始的而是從2開始,這是爲啥嘞,咱們來分析一下:
在此以前咱們先來跑另一段代碼:
function printer() { $i = 1; while(true) { echo 'this is the yield ' . (yield $i) . "\n"; $i++; } } $printer = printer(); var_dump($printer->current()); var_dump($printer->send('first')); var_dump($printer->send('second'));
這個時候咱們會發現執行的結果變成了:
int(1) this is the yield first int(2) this is the yield second int(3)
能夠看到在第一次調用生成器函數的時候,生成器已經執行到了第一個yield表達式
處,因此在$printer->send('first')
以前,生成器便已經yield 1
出來了,只是沒有對這個生成的值進行接收處理,在send()
了以後,echo
語句便會緊接着完整的執行,執行完畢繼續執行$i++
,下次循環即是var_dump(2)
。
至此,咱們看到了yield
不只可以返回
數據並且還能夠接收
數據,並且二者能夠同時進行,此時yield
便成了數據雙向傳輸的工具,這就爲了實現協程
提供了可能性。
至於接下來的協程的知識,水平有限很差介紹,仍是看鳥哥的原文比較直接,裏面例子很豐富,介紹的很詳盡。