PHP的生成器、yield和協程

PHP的生成器、yield和協程

雖然以前就接觸了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()獲取一個數組而後遍歷數組,並且還能夠在對象中對數據進行預處理。性能

yield和生成器

相比較迭代器,生成器提供了一種更容易的方法來實現簡單的對象迭代,性能開銷和複雜性都大大下降。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便成了數據雙向傳輸的工具,這就爲了實現協程提供了可能性。

至於接下來的協程的知識,水平有限很差介紹,仍是看鳥哥的原文比較直接,裏面例子很豐富,介紹的很詳盡。

相關文章
相關標籤/搜索