php curl header_PHP中的yield与协程(二十一节)
大家好,我是老李。
順風說騷話,逆風講道理
最近在大家一起努力下,那個沙雕肺炎患病人數增長率下降了不少,總體來說還算順,所以今天這篇注定又要騷話連篇了。聽說最近不少玉米開始向大連、威海、煙臺方向涌入,算是潛在的風險吧,總之,大家也都別放松警惕,不要看到曙光時候給撂倒了。
上一節課我們說的主要是在謝頂道人 --- 老李的提示下,你初步使用了yield...那個你沒有名字也不好,給你起個名字,洋氣點兒就叫歐陽吧,一聽就是個貴族富少繼承者們。
喊人先喊親,要把讀者叫舒心
文章別嫌少,得把客官伺候好
開工第一天,你的老板原上草又交給你一個任務并信誓旦旦地答應你如果你能順利完成任務那么這個月的打卡遲到費就不扣你的了。強大的利好信息讓你欲罷不能。事情是這樣的,還是上一集那個內存只有100KB的單片機,這個單片機會訪問三方服務公司的一個API,但有時候這個API會抽風,老板原上草的意思是這會兒不要阻塞等待而是讓這個單片機干點兒別的事兒,等API訪問OK了再讓TA回來,總之別讓TA閑著,算是免費送這個單片機一個滿滿的福報。
考慮到昨天謝頂道人曾經給你科普過的yield似乎擁有一種讓出CPU實現用戶調度的能力,你決定展現一波兒自我,而謝頂道人也決定用那雙充滿了老繭子的手手把手輔導你。
<?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不到發生了什么,是嗎?就是gen1()和gen2()可以交替運行并且每次都是接著從上次的地方開始運行,你要用傳統的function是完全做不到的,傳統的function只能一口氣先完成其中一個函數中的for()然后再能完成另外一個function中的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
好了歐陽,讓我們展示真正的技術吧!下面這個demo,如果訪問某個API阻塞的話就主動讓出CPU,然后讓出的CPU開始往一個文件里寫字符串...反正不能讓TA閑著:
<?php $ch1 = curl_init();// 這個地址中的php,我故意sleep了5秒鐘,然后輸出一坨jsoncurl_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 );//?gen1中就是調用三方API,基于multi-curl實現function gen1( $mh, $ch1 ) { do { $mrc = curl_multi_exec( $mh, $running ); // 請求發出后,讓出cpu $rs = yield;????//?生產環境千萬別這么干......????// 這里加sleep是為了讓你看的更清楚流程 sleep( 1 ); echo "收到外部發送數據{$rs}".PHP_EOL; } while( $running > 0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false;}//?gen2是寫文件...function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i.PHP_EOL, FILE_APPEND ); $rs = yield; // 生產環境千萬別這么干...... // 這里加sleep是為了讓你看的更清楚流程 sleep( 1 ); echo "收到外部發送數據{$rs}".PHP_EOL; }}$gen1 = gen1( $mh, $ch1 );$gen2 = gen2();while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->send("gen1"); $gen2->send("gen2");}上面這坨代碼在飛起來后,我們再等待curl發起請求的5秒鐘內,同時可以完成文件寫入功能,如果換做平時的PHP程序,就只能是先阻塞等待curl拿到結果后才能完成文件寫入,有了一絲絲內味兒了嗎?
協程味兒
但是這里必須要值得注意的是,歐陽在gen1()的代碼里用的并不是我們一般時候用的curl方法,而是curl_multi_exec(),為啥呢?因為一般般我們最常用的PHP curl方法都是阻塞的,這很致命,這里要點就是:全程不能阻塞,阻塞一處死翹翹。實際上這里最標準的用法就是curl_multi_exec()配合curl_multi_select()。所以,擴散一下思維如果你用file_get_contents()也是不行的。
下面由謝頂道人總結一個PHP中yield的典型使用方法:如果要使用yield實現「異步」,實際上在PHP里也只能是結合select或epoll這些IO服用,具體就是當IO沒有ready的時候,yield出讓CPU去做別的事情,一旦IO ready了就回來繼續執行原來的任務,說白了就是協程調度器!
???
那TM我要這yield到底有啥用?謝頂道人你咋這幽默呢?感覺蒙娜麗莎都是你逗笑的呢~我直接用之前章節里基于libevent實現的服務器不就挺好用的嗎?這里要說的就是「基于IO復用實現的異步非阻塞服務器中難以避免的異步回調地獄」寫法,說白了就是一層又一層嵌套的on。這在NodeJS里頗為常見,所以后來NodeJS出了一個叫做Promise的關鍵字來緩解這個問題,這里你可以粗暴的認為yield就是PHP版本的Promise,就是傳說中的「用傳統同步代碼的寫法寫異步」,但也依然能寫出高IO的程序。
世面上有什么典型作品嗎?有啊,swoole呀,swoole協程就是基于epoll實現的協程調度器;還有微信開源的libco也基本上是基于IO復用實現的協程調度器。要注意的基于epoll實現協程調度器只是一種實現方式而已,像Golang則是完全是自己在上層實現的調度器。
好看的皮囊就是好看,有趣的靈魂愛咋咋滴...
總結
以上是生活随笔為你收集整理的php curl header_PHP中的yield与协程(二十一节)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中break和continu
- 下一篇: 【LeetCode笔记】283. 移动零