首先是,這是我第一次把公衆號文章複製粘貼到sf.gg來。php
其次是,好久好久以前,我挖了一個yield的一個坑,本身挖的坑本身填,否則早晚會把本身埋掉。html
最後是,若是想看以前那個坑,請發送「yield」給文章末尾的公衆號,我開通了高大上的自動回覆功能,稀罕地不得了!node
PS:那篇文章中在最後我犯了一個錯誤,誤下了一個結論:foreach中不能使用send並猜想這是PHP的bug,實際上並非,真實的緣由粗暴簡單的理解就是send會讓生成器繼續執行一次致使。這件事情告訴咱們:git
除了裝逼以外,甩鍋也是有打臉風險的
那篇坑裏,內容和你能在百毒上搜索到的大多數文章都是差很少的,不過我那篇坑標題起得好:《yield是個什麼玩意(上)》,也就是暗示你們還有下篇,因此起標題也是須要必定技術含量的。github
我堅信,在座的各位辣雞在看完上篇坑文後最想說的註定是泰迪熊這句話(這是文化屬性,不以各位的意志而轉移):json
回到今天主旨上來,強調幾點:服務器
多線程和多進程都是操做系統參與的調度,而協程是用戶自主實現的調度,協程的關鍵點其實是「用戶層實現自主調度」,大概有「翻身農奴把歌唱」的意思。微信
下面我經過一坨代碼來體會一把「翻身農奴」,大家感覺一下:網絡
<?php function gen1() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN1 : {$i}".PHP_EOL; // sleep沒啥意思,主要就是運行時候給你一種切實的調度感,你懂麼 // 就是那種「你看!你看!尼瑪,我調度了!臥槽」 sleep( 1 ); // 這句很關鍵,表示本身主動讓出CPU,我不下地獄誰下地獄 yield; } } function gen2() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN2 : {$i}".PHP_EOL; // sleep沒啥意思,主要就是運行時候給你一種切實的調度感,你懂麼 // 就是那種「你看!你看!尼瑪,我調度了!臥槽」 sleep( 1 ); // 這句很關鍵,表示本身主動讓出CPU,我不下地獄誰下地獄 yield; } } $task1 = gen1(); $task2 = gen2(); while( true ) { // 首先我運行task1,而後task1主動下了地獄 echo $task1->current(); // 這會兒我可讓task2介入進來了 echo $task2->current(); // task1恢復中斷 $task1->next(); // task2恢復中斷 $task2->next(); }
上面代碼執行結果以下圖:多線程
雖然我話都說到這裏了,可是確定仍是有人get不到「因此,到底發生了什麼?」。你要知道,若是function gen1和function gen2中沒有yield,而是普通函數,你是沒法中斷其中的for循環的,諸以下面這樣的代碼:
<?php function gen1() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN1 : {$i}".PHP_EOL; sleep( 1 ); } } function gen2() { for( $i = 1; $i <= 10; $i++ ) { echo "GEN2 : {$i}".PHP_EOL; } } gen1(); gen2(); // 看這裏,看這裏,看這裏! // 上面的代碼一旦運行,必定是先運行完gen1函數中的for循環 // 其次才能運行完gen2函數中的for循環,絕對不會出現 // gen1和gen2交叉運行這種狀況
我彷佛已然精通了yield
寫到這裏後我也開始蹩了,和以往的憋了三天蹦不出來個屁有所不一樣,我此次蹩出了一個比較典型的應用場景:curl。下面咱們基於上面那坨辣雞代碼將gen1修改成一個耗時curl網絡請求,gen2將向一個文本文件中寫內容,咱們的目的就是在耗時的curl開始後主動讓出CPU,讓gen2去寫文件,以實現CPU的最大化利用。
<?php $ch1 = curl_init(); // 這個地址中的php,我故意sleep了5秒鐘,而後輸出一坨json curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" ); curl_setopt( $ch1, CURLOPT_HEADER, 0 ); $mh = curl_multi_init(); curl_multi_add_handle( $mh, $ch1 ); function gen1( $mh, $ch1 ) { do { $mrc = curl_multi_exec( $mh, $running ); // 請求發出後,讓出cpu yield; } while( $running > 0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); yield; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->next(); $gen2->next(); }
上面的代碼,運行之後,咱們再等待curl發起請求的5秒鐘內,同時能夠完成文件寫入功能,若是換作平時的PHP程序,就只能是先阻塞等待curl拿到結果後才能完成文件寫入。
文章太長,就像「老太太的裹腳布同樣,又臭又長」,因此,最後再對代碼作個極小幅度的改動就收尾不寫了!
<?php $ch1 = curl_init(); // 這個地址中的php,我故意sleep了5秒鐘,而後輸出一坨json curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" ); curl_setopt( $ch1, CURLOPT_HEADER, 0 ); $mh = curl_multi_init(); curl_multi_add_handle( $mh, $ch1 ); function gen1( $mh, $ch1 ) { do { $mrc = curl_multi_exec( $mh, $running ); // 請求發出後,讓出cpu $rs = yield; echo "外部發送數據{$rs}".PHP_EOL; } while( $running > 0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); $rs = yield; echo "外部發送數據{$rs}".PHP_EOL; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->send("gen1"); $gen2->send("gen2"); }
咱們修改了內容:
將$gen1->next()修改爲了$gen1->send("gen1")
在function gen1中yield有了返回值,而且將返回值打印出來
這件事情告訴咱們:yield和send,是能夠雙向通訊的,同時告訴咱們send能夠用來恢復原來中斷的代碼,並且在恢復中斷的同時能夠攜帶信息回去。寫到這裏,你是否是以爲這玩意的可利用價值是否是比原來高點兒了?
我知道,有人確定叨叨了:「老李,你代碼特麼寫的真是辣雞啊!你以前保證過了的 --- 只在公司生產環境寫辣雞代碼的。可你看看你這辣雞光環到籠罩都到demo裏了,你連demo都不放過了!你怎麼說?!」。兄dei,「又不是不能用」。並且我告訴你,上面這點兒curl demo來說明白yield仍是不夠的,後面還有兩三篇yield呢,照樣是爛代碼噁心死你,愛看不看。我勸你心放寬,你想一想你這麼爛的代碼都經歷了,還有什麼不能經歷的?
文章最後補個小故事:其實yield是PHP 5.5就已經添加進來了,這個模塊的做者叫作Nikita Popov,網絡上的名稱是Nikic。咱們知道PHP7這一代主力是惠新宸,下一代PHP主力就是Nikic了。早在2012年,Nikic就發表了一篇關於PHP yield多任務的文章,連接我貼出來你們共賞一下 --- http://nikic.github.io/2012/1...