1. 程式人生 > 實用技巧 >[轉]php 中yield是個什麼東西

[轉]php 中yield是個什麼東西

原文:https://segmentfault.com/a/1190000018457194

感受一下,下面這段程式碼的執行順序? 體會協程排程?? “進進出出”?

<?php
function gen() {
    $ret = (yield 'yield1');
    var_dump($ret);
    echo "next-------\n";
    $ret = (yield 'yield2');
    var_dump($ret);
   $tt= yield "zzzzzzzzz";
  var_dump($tt);
}
$gen = gen();
var_dump($gen->current());    // string(6) "yield1"
//var_dump($gen->send('ret1')); // string(4) "ret1"   (the first var_dump in gen)
  $gen->send('ret1');
  echo "before, next----\n";
  $gen->next();
echo "next-------af\n";
  $gen->send('ret2');                            // string(6) "yield2" (the var_dump of the ->send() return value)
//var_dump($gen->send('ret2')); // string(4) "ret2"   (again from within gen)
$gen->send(null);                              // NULL               (the return value of ->send())
echo "111\n";
?>
                                                                                                                                                  

  

rockman

最後的原因應該是 foreach 自動隱含了 next 操作,然後 send 本身也會觸發一次 next,所以才造成資料被跳過了

2回覆3月4日
  • DEMO

    沒錯,foreach的本質是內部移動指標,會隱式的呼叫next方法

——————————————————————————————————————————————

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

協程yieldphp 釋出於 2019-03-11

其實,我並不是因為迭代或者生成器或者研究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,自己動手試下吧