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

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

那麼,問題來了,這東西究竟是有何用?node

先來感覺一個問題,給你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,大家感覺一下:spa

528440bytes,約莫就是528Kb,幾乎是100Kb的五倍了,媽的這日子無法過了。操作系統

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

人都是被逼出來的,因而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,本身動手試下吧。

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

圖片描述

相關文章
相關標籤/搜索