[轉]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,自己動手試下吧