PHP的yield是個什麼玩意(一)

其實,我並不是因爲迭代或者生成器或者研究PHP手冊才認識的yield,要不是協程,我到現在也不知道PHP中還有yield這麼個鬼東西。人家這個東西是從PHP 5.5就開始引入了,官方名稱叫做生成器。你要說爲什麼5.5年代的東西,現在纔拿出來。我還想問你喲,PHP 5.3就有了的namespace爲毛到最近這幾年纔開始正式投產。

那麼,問題來了,這東西到底是有何用?

先來感受一個問題,給你100Kb的內存(是的,你沒有看錯,就是100Kb),然後讓你迭代輸出一個從1開始一直到10000的數組,步進爲1。

愈先迭代數組,必先創造數組。

所以,腦門一拍,代碼一坨如下:

<?php
$start_mem = memory_get_usage();
$arr = range( 1, 10000 );
foreach( $arr as $item ){
  //echo $item.',';
}
$end_mem = memory_get_usage();
echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;

一頓操作猛如虎,運行一下成績1-5,你們感受一下:

528440bytes,約莫就是528Kb,幾乎是100Kb的五倍了,媽的這日子沒法過了。

畢竟你們也知道,最近內存價格確實貴,國家也在號召低碳節能減排,你多耗費5倍內存,就意味着多排放5倍的二氧化碳,就意味着要爲多用的內存多花錢貢獻給棒子... ...你想想,那可是棒子。

人都是被逼出來的,於是yield可以來救場了,大概代碼如下,注意看操作:

<?php
$start_mem = memory_get_usage();
function yield_range( $start, $end ){
  while( $start <= $end ){
    $start++;
    yield $start;
  }
}
foreach( yield_range( 0, 9999 ) as $item ){
  echo $item.',';
}
$end_mem = memory_get_usage();
echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;

運行一下,你們感受一下:

首先,我們觀察一下yield_range這個函數跟普通函數不一樣的地方,就是普通函數往往都是使用return來返回結果,而這個中則是yield。其次是普通函數中return只能返回一次,這個yield能返回好多次。

那麼,我們來分析一波兒這個神奇的yield_range函數。這個yield關鍵字到底返回的是什麼?我們簡單看一下:

<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $start++;
    yield $start;
  }
}
$rs = yield_range( 1, 100 );
var_dump( $rs );
/*
object(Generator)#1 (0) {
}
*/

yield返回的是一個叫做Generator(中文名就是生成器)的object對象,而這個生成器是實現了Iterator接口(至於Iterator接口,你們去PHP手冊上搜索吧)。所以,既然實現了Iterator接口(也正是因爲如此,這個東西可以使用foreach進行迭代,明白了吧?),所以可以有如下代碼:

<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    yield $start;
    $start++;
  }
}
$generator = yield_range( 1, 10 );
// valid() current() next() 都是Iterator接口中的方法
while( $generator->valid() ){
  echo $generator->current().PHP_EOL;
  $generator->next();
}

運行結果如下所示:

重點來了:這個yield_range函數似乎能夠記住它上一次運行到哪兒了,上一次運行的結果是什麼,然後緊接着在下一次運行的時候繼續從上次終止的地方繼續開始。這不是普通的PHP函數可以做得到的!

我們知道,操作系統在調度進程的時候,會觸發一個叫做“進程上下文切換”的概念。比如CPU從進程A調度給進程B了,那麼當再次從進程B調度給進程A的時候,當初進程A運行到哪兒了、臨時的數據結果是什麼都是需要被還原的,不然,一切都要從頭,那就要出大問題了。而,這個yield關鍵字,似乎在用戶態(非系統內核級)就可以實現這個概念。所以說,用yield搞迭代,怕是真的很沒出息的一件事,它能做的太多。

緊接着,我們需要認識一個生成器對象的一個方法,叫做send,簡單看下下面這坨代碼:

<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $ret = yield $start;
    $start++;
    echo "yield receive : ".$ret.PHP_EOL;
  }
}
$generator = yield_range( 1, 10 );
$generator->send( $generator->current() * 10 );

運行結果如圖所示:

send方法可以修改yield的返回值,但是,你也不能想當然,比如下面這坨代碼,你們以爲運行結果是什麼樣呢?

<?php
function yield_range( $start, $end ){
  while( $start <= $end ){
    $ret = yield $start;
    $start++;
    echo "yield receive : ".$ret.PHP_EOL;
  }
}
$generator = yield_range( 1, 10 );
foreach( $generator as $item ){
  $generator->send( $generator->current() * 10 );
}

本來以爲運行結果是類似於這樣的:

<?php
yield receive : 10
yield receive : 20
yield receive : 30
yield receive : 40
yield receive : 50
yield receive : 60
yield receive : 70
yield receive : 80
yield receive : 90
yield receive : 100

然而,唯物主義告訴我們:

結果是打臉的,你們感受一下:

原因是什麼呢?原因是當你在外部向yield發送send的時候,會自動觸發一次next,自己動手試下吧。

最近開了一個微信公衆號,所有文章都在這裏(手賤弄成服務號了)

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章