天天看點

PHP中的yield與協程(二十節)

大家好,我是誠實且憨厚而又不失優雅的老李,我完美地太監了十二天。

我遇到一件比較逗逼的事情,我簡單說說你們感受一下,自從我開公衆号後文章的難度系數大概是從容易到逐漸變難,閱讀量逐漸呈現降低的趨勢。現在基本上都維持在每篇大概200閱讀量左右,但是上周六我發了一篇關于回京的文章,1076的閱讀量顯得「雞立鶴群」,場面也一度十分尴尬...

PHP中的yield與協程(二十節)

這個資料讓我開始懷疑人生,但在經過了一番痛定思痛以及思考後,我認為是我這篇文章蹭了肺炎的熱點流量。很顯然,我堅信大家還是一定非常熱愛學習熱愛讀技術文章的:

PHP中的yield與協程(二十節)

盡管閱讀量一再新低,但是這《PHP網絡程式設計》還是得堅持寫下去一直到寫完,主要是找不到接盤的英雄。我看了下目錄,這本書我已經完成大概四分之三了,這周如果能再猛押一口恒河水,勁頭一上來估計這周就能完成了。

衆所周知(大概幾十個人知道)老李之前是寫過關于PHP的yield的,一共寫了兩篇而是算是上下篇關系,本來還打算寫第三篇但是卻像快刀斬亂麻般得太監了,并不是因為我懶,而是我發現如果要寫好第三篇PHP的yield必須要鋪墊一大堆關于IO的基礎知識才行,現如今忽如一夜春風來、玉樹流光照後庭,條件允許了一切都成熟飽滿了,請讓我開始複讀PHP的yield。

Yield是PHP 5.5之後引入的新功能,其實隔壁家的Python也有這個玩意。有一天你的老闆拿着一個記憶體隻有100KB的智能硬體,這個硬體的功能就是不斷從1循環到10000,你急不可耐、動手動腳,很快拍了拍油光锃亮的腦袋活生生憋出來了一段代碼:

<?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 ) / 1024.'bytes'.PHP_EOL;           

複制

胡粘代碼猛如虎,然後一運作成績負分滾粗:

PHP中的yield與協程(二十節)

拿電腦一算516KB記憶體幾乎快是100KB記憶體的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 )/1024.'bytes'.PHP_EOL;           

複制

一運作,有點兒意思!代碼風騷、效率驚人,連TM記憶體機關都精确到bytes了:

PHP中的yield與協程(二十節)

卧槽,這yield是何方妖孽?

首先觀摩一下yield_range()「函數」,和傳統函數差別就是傳統函數中用return關鍵字結束,而yield_range()「函數」使用yield關鍵字結束,是以實際上這坨飽含了yield的代碼已經不能稱之為函數了;而且還有就是普通的函數你調用一次就結束了,代碼段中局部變量一次發射完畢,而yield看起來可以調用多次可以保持其中的局部變量的值與狀态。

那麼這個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) {
//}           

複制

Generator!Generator!Generator!我叫你三聲,你敢答應嗎?

Generator就是傳說中的生成器,毫無疑問這玩意也是跟随着PHP 5.5誕生而誕生的,而且TA實作了Iterator接口,難怪上面demo裡能直接對傳回結果進行foreach呢~~~既然實作了Iterator接口,那麼滿足Iterator接口的各種花式騷操作都可以用在Generator上了,比如:

<?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();
}           

複制

PHP中的yield與協程(二十節)

這個yield_range()有點兒神了,TA似乎能記住變量$start數值目前狀态,簡單說就是第N次調用時候變量$start的值為X,而第N+1次調用的時候變量$start的值為X+1。

你是不是想到了類似于作業系統中程序上下文切換?程序A在某個時刻被CPU停止,然後排程程序B開始跑,然後停止程序B後重新開始跑程序A,那麼程序A再次從「就緒态」輪換到「運作态」的時候,一切的一切都還要從上次停止的時候繼續(注意是繼續)開始,提了褲子不認人?不存在的...隻不過說程序上下文切換,說到底是作業系統完成,而且好像也沒有什麼API接口之類的可以讓我們直接使用這個功能,而這個yield似乎在使用者态就實作了這個功能,于是這就給了我們一種搞騷操作的一種可能性。

上面的demo裡,我們已經接觸了Generator生成器的好幾個方法,比如valid()、current()、next()等,然後我們再說一個更重要的方法: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:".$generator->current() * 10 );           

複制

愉快的跑一波兒:

PHP中的yield與協程(二十節)

這個send()使得我們擁有另外一個能力:與Generator進行資料互動。此前的demo都是我們從Generator中擷取資料,現如今send()方法可以向Generator發射資料,這就叫持槍互射。

上面代碼我們xue微改一下,然後我改你們猜,猜下結果好乏?

<?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:".$generator->current() * 10 );
}           

複制

猜好了?我揭鍋了昂... ...啦啦啦~~~

PHP中的yield與協程(二十節)

很明顯,這個一定大機率地诠釋了什麼叫意外、什麼叫驚喜。相對于喜當爹那種意外和驚喜,yield Generator這種驚喜在意外中又帶着一絲絲理性邏輯的拷問。

意外與驚喜

人在家中坐,女神忽叫我;

問女神啥事,女神欲還說;

問女何所思,女神掩面泣;

問女何所憶,女神哭啼啼;

我若嫁于你,你願共伴離?

如若願嫁我,我一生相許;

女神破涕笑,笑顔恍若桃;

明到你住所,你休要欺我~

我内心蕩漾,然表面逞強。

...

..

.

那個,咳咳,不好意思跑題了跑題了,今天先編到這兒,明天我再發yield Generator的第二篇接着寫完這首詩,這個即興發揮有點兒收不住了。

接着說上面demo為啥會出現這種情況呢?最早的那會兒,大概一兩年前吧,我一直以為這是一個bug,後來才發現實際上并不是,這個是一個預期的行為,具體說就是當你調用Generator的send()時,會自動觸發一次next()。

yield基礎使用方法普及完畢,明天第二篇開始應用。