亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    了解PHP yield的高級(jí)用法

    了解PHP yield的高級(jí)用法

    開(kāi)篇

    剛開(kāi)始接觸PHPyield 的時(shí)候,感覺(jué),yield 是什么黑科技,百度一下:yield——協(xié)程,生成器。很多文章都在講 IteratorGenerater, 蛤~,這東西是 PHP 迭代器的一個(gè)補(bǔ)充。再翻幾頁(yè),就是Go 協(xié)程。我出于好奇點(diǎn)開(kāi)看了下Go 協(xié)程, 里面都是 并發(fā),線(xiàn)程,管道通訊這類(lèi)字眼,wc,nb, 這tm才是黑科技啊,再回來(lái)看PHP,分分鐘想轉(zhuǎn) Go。

    相關(guān)學(xué)習(xí)推薦:PHP編程從入門(mén)到精通

    yield 語(yǔ)法加入 PHP

    yield語(yǔ)法是在版本5.5加入PHP的,配合迭代器使用,功能上就是 流程控制 代碼,和goto,return 類(lèi)似。

    以下就是官方提供的 yield 小例子,通過(guò)執(zhí)行結(jié)果,我們可分析當(dāng)代碼執(zhí)行到 yield $i 時(shí),他會(huì)進(jìn)行 return $i, 待 echo "$valuen" 后, goto for ($i = 1; $i <= 3; $i++) {, 對(duì)!PHP 的 yield 就是一個(gè)能出能進(jìn)的語(yǔ)法。在z代碼中七進(jìn)七出,把 $i 平平安安得送了出來(lái)。

    <?phpfunction gen_one_to_three() {     for ($i = 1; $i <= 7; $i++) {         //注意變量$i的值在不同的yield之間是保持傳遞的。         yield $i;     }}$generator = gen_one_to_three();foreach ($generator as $value) {     echo "$valuen";}// output12...67

    我們遇到了什么問(wèn)題

    寫(xiě)代碼就是解決問(wèn)題。我們來(lái)看看他們遇到了什么問(wèn)題:php官方呢,需要言簡(jiǎn)意賅地把yield介紹給大家。一部分網(wǎng)友呢,需要在有限的資源內(nèi)完成大文件操作。而我們的鳥(niǎo)哥。面對(duì)的一群對(duì)當(dāng)下yield的教程停留于初級(jí)而不滿(mǎn)意的phper,就以一個(gè)任務(wù)調(diào)度器作為例子,給大家講了一種yield高級(jí)用法。

    php.net:生成器語(yǔ)法,
    PHP如何讀取大文件,
    風(fēng)雪之隅:在PHP中使用協(xié)程實(shí)現(xiàn)多任務(wù)調(diào)度.

    提出問(wèn)題,再用yield來(lái)解答,看到以上答案,我覺(jué)得呢,這PHP協(xié)程不過(guò)如此(Go協(xié)程相比 )。

    有句話(huà)——一個(gè)好問(wèn)題比答案更重要,目前廣大網(wǎng)友還沒(méi)有給yield提出更好,更困難的問(wèn)題。

    yield這個(gè)進(jìn)進(jìn)出出的語(yǔ)法,很多舉例都是再讓yield做迭代器啊,或者利用低內(nèi)存讀取超大文本的Excel,csv什么的,再高級(jí)就是用它實(shí)現(xiàn)一個(gè)簡(jiǎn)單的任務(wù)調(diào)度器,并且這個(gè)調(diào)度器,一看代碼都差不多。

    我來(lái)出道題

    正如一個(gè)好的問(wèn)題,比答案更有價(jià)值

    1. 用PHP實(shí)現(xiàn)一個(gè) Socket Server,他能接收請(qǐng)求,并返回Server的時(shí)間。

    好,這是第一個(gè)問(wèn)題,鋪墊。 官方答案

    1. 在原來(lái)的代碼上,我們加個(gè)需求,該Socket Server 處理請(qǐng)求時(shí),依賴(lài)其他 Socket Server,還需要有 Client 功能。也就是他能接收請(qǐng)求,向其它Server發(fā)起請(qǐng)求。

    這是第二個(gè)問(wèn)題,也是鋪墊。

    1. 原來(lái)的Socket Server同一時(shí)間只能服務(wù)一個(gè)客戶(hù),希望能實(shí)現(xiàn)一個(gè) 非阻塞I/O Socket Server, 這個(gè) Server 內(nèi)有 Socket Client 功能,支持并發(fā)處理收到的請(qǐng)求,和主動(dòng)發(fā)起的請(qǐng)求。要求不用多線(xiàn)程,多進(jìn)程。

    這個(gè)問(wèn)題,還是鋪墊,這幾個(gè)問(wèn)題很干,大家可以想一想,2,3題的答案,都放在一個(gè)腳本里了:nio_server.php

    以上這段代碼,我列舉了一個(gè)具體的業(yè)務(wù),就是用戶(hù)請(qǐng)求購(gòu)物車(chē)加購(gòu)動(dòng)作, 而購(gòu)物車(chē)服務(wù)呢,又需要和 產(chǎn)品服務(wù),庫(kù)存服務(wù),優(yōu)惠服務(wù) 交互,來(lái)驗(yàn)證加購(gòu)動(dòng)作可行性。有同步,異步方式請(qǐng)求,并做對(duì)比。

    后續(xù)還有很多代碼,我都放gitee鏈接了。使用方法,見(jiàn)readme.md

    1. 最后一個(gè)問(wèn)題:在PHP中,用同步寫(xiě)代碼,程序呢異步執(zhí)行?需要怎么調(diào)整代碼。

    提示:這個(gè)和 PHPyield 語(yǔ)法有關(guān)。

    再提示:yield 語(yǔ)法特征是什么,進(jìn)進(jìn)出出!

    看著我們的代碼,同步, 異步,進(jìn)進(jìn)出出 你想到了什么?

    看到代碼,同步處理模式下,這三個(gè)函數(shù)checkInventory checkProduct checkPromo 時(shí),發(fā)起請(qǐng)求,并依次等待返回的結(jié)果,這三個(gè)函數(shù)執(zhí)行后,再響應(yīng)客戶(hù)請(qǐng)求。

    異步處理模式下,這三個(gè)函數(shù)發(fā)起請(qǐng)求完畢后,代碼就跳出循環(huán)了,然后是在select()下的一個(gè)代碼分支中接收請(qǐng)求, 并收集結(jié)果。每次收到結(jié)果后判斷是否完成,完成則響應(yīng)客戶(hù)端。

    那么能不能這樣:在異步處理的流程中,當(dāng) Server收到 自己發(fā)起的 client 有數(shù)據(jù)響應(yīng)后,代碼跳到 nio_server.php 的 247行呢,這樣我們的收到請(qǐng)求校驗(yàn)相關(guān)的代碼就能放到這里,編碼能就是同步,容易理解。不然,client 的響應(yīng)處理放在 280 行以后,不通過(guò)抓包,真的很難理解,執(zhí)行了第 247 行代碼后,緊接著是從 280 行開(kāi)始的。

    誒~這里是不是有 進(jìn)進(jìn)出出 那種感覺(jué)了~ 代碼從 247 行出去,開(kāi)始監(jiān)聽(tīng)發(fā)出 Client 響應(yīng),收到返回?cái)?shù)據(jù),帶著數(shù)據(jù)再回到 247 行,繼續(xù)進(jìn)行邏輯校驗(yàn),綜合結(jié)果后,再響應(yīng)給客戶(hù)端。

    用yield來(lái)解決問(wèn)題

    基于 yield 實(shí)現(xiàn)的,同步編碼,"異步"I/OSocket Server 就實(shí)現(xiàn)了。代碼。

    這里 “異步” 打了引號(hào),大佬別扣這個(gè)字眼了。 該是非阻塞I/O

    不等大家的答案了,先上我的結(jié)果代碼吧,代碼呢都放在這個(gè)目錄下了。

    gitee https://gitee.com/xupaul/PHP-generator-yield-Demo/tree/master/yield-socket

    運(yùn)行測(cè)試代碼

    clone 代碼到本地后,需要拉起4個(gè) command 命令程序:

    拉起3個(gè)第三方服務(wù)

    ## 啟動(dòng)一個(gè)處理耗時(shí)2s的庫(kù)存服務(wù)$ php ./other_server.php 8081 inventory 2## 啟動(dòng)一個(gè)處理耗時(shí)4s的產(chǎn)品服務(wù)$ php ./other_server.php 8082 product 4## 監(jiān)聽(tīng)8083端口,處理一個(gè)請(qǐng)求 耗時(shí)6s的 promo 服務(wù)$ php ./other_server.php 8083 promo 6

    啟動(dòng)購(gòu)物車(chē)服務(wù)

    ## 啟動(dòng)一個(gè)非阻塞購(gòu)物車(chē)服務(wù)$ php ./async_cart_server.php   ## 或者啟動(dòng)一個(gè)一般購(gòu)物車(chē)服務(wù)$ php ./cart_server.php

    發(fā)起用戶(hù)請(qǐng)求

    $ php ./user_client.php

    運(yùn)行結(jié)果呢如下,通過(guò)執(zhí)行的時(shí)間日志,可得這三個(gè)請(qǐng)求是并發(fā)發(fā)起的,不是阻塞通訊。

    在看我們的代碼,三個(gè)函數(shù),發(fā)起socket請(qǐng)求,沒(méi)有設(shè)置callback,而是通過(guò)yield from 接收了三個(gè)socket的返回結(jié)果。

    也就是達(dá)到了,同步編碼,異步執(zhí)行的效果。

    運(yùn)行結(jié)果

    非阻塞模式

    client 端日志:
    了解PHP yield的高級(jí)用法

    通過(guò)以上 起始時(shí)間結(jié)束時(shí)間 ,就看到這三個(gè)請(qǐng)求耗時(shí)總共就6s,也就按照耗時(shí)最長(zhǎng)的promo服務(wù)的耗時(shí)來(lái)的。也就是說(shuō)三個(gè)第三方請(qǐng)求都是并發(fā)進(jìn)行的。

    cart server 端日志:
    了解PHP yield的高級(jí)用法

    而 cart 打印的日志,可以看到三個(gè)請(qǐng)求一并發(fā)起,并一起等待結(jié)果返回。達(dá)到非阻塞并發(fā)請(qǐng)求的效果。

    阻塞模式

    client 端日志:
    了解PHP yield的高級(jí)用法

    以上是阻塞方式請(qǐng)求,可以看到耗時(shí) 12s。也就是三個(gè)服務(wù)加起來(lái)的耗時(shí)。

    cart server 端日志:
    了解PHP yield的高級(jí)用法

    cart 服務(wù),依次阻塞方式請(qǐng)求第三方服務(wù),順序執(zhí)行完畢后,共耗時(shí)12s,當(dāng)然如果第一個(gè),獲第二個(gè)服務(wù)報(bào)錯(cuò)的話(huà),會(huì)提前結(jié)束這個(gè)檢查。會(huì)節(jié)約一點(diǎn)時(shí)間。

    工作原理

    這里就是用到了 yield 的工作特點(diǎn)——進(jìn)進(jìn)出出,在發(fā)起非阻塞socket請(qǐng)求后,不是阻塞方式等待socket響應(yīng),而是使用yield跳出當(dāng)前執(zhí)行生成器,等待有socket響應(yīng)后,在調(diào)用生成器的send方法回到發(fā)起socket請(qǐng)求的函數(shù)內(nèi),在 yield from Async::all() 接收數(shù)據(jù)響應(yīng)數(shù)據(jù)搜集完畢后,返回。

    和Golang比一比

    考慮到網(wǎng)速原因,我這就放上一個(gè)國(guó)內(nèi)教程鏈接:Go 并發(fā) 教程

    php的協(xié)程是真協(xié)程,而Go是披著協(xié)程外衣的輕量化線(xiàn)程(“協(xié)程”里,都玩上“鎖”了,這就是線(xiàn)程)。

    我個(gè)人偏愛(ài),協(xié)程的,覺(jué)得線(xiàn)程的調(diào)度有一定隨機(jī)性,因此需要鎖機(jī)制來(lái)保證程序的正確,帶來(lái)了額外開(kāi)銷(xiāo)。協(xié)程的調(diào)度(換入換出)交給了用戶(hù),保證了一段代碼執(zhí)行連續(xù)性(當(dāng)然進(jìn)程級(jí)上,還是會(huì)有換入換出的,除非是跨進(jìn)程的資源訪問(wèn),或者跨機(jī)器的資源訪問(wèn),這時(shí),就要用到分布式鎖了,這里不展開(kāi)討論),同步編碼,異步執(zhí)行,只需要考慮那個(gè)哪個(gè)方法會(huì)有IO交互會(huì)協(xié)程跳出即可。

    和NodeJS比劃一下

    Javascript 和 PHP 兩個(gè)腳本語(yǔ)言有很多相似的地方,弱類(lèi)型,動(dòng)態(tài)對(duì)象,單線(xiàn)程,在Web領(lǐng)域生態(tài)豐富。不同的是,Javascript在瀏覽器端一開(kāi)始就是異步的(如果js發(fā)起網(wǎng)絡(luò)請(qǐng)求只能同步進(jìn)行,那么你的網(wǎng)頁(yè)渲染線(xiàn)程會(huì)卡?。?,例如AjaxsetTimeoutsetInterval,這些都是異步+回調(diào)的方式工作。

    基于V8引擎而誕生的NodeJS,天生就是異步的,在提供高性能網(wǎng)絡(luò)服務(wù)有很大的優(yōu)勢(shì),不過(guò)它的IO編碼范式么。。。剛開(kāi)始是 回調(diào)——?dú)У舻鬲z,后來(lái)有了Promise——屏幕豎起來(lái)看,以及Generator——遇事不絕yield一下吧,到現(xiàn)在的Async/Await——語(yǔ)法糖?真香!

    可以說(shuō)JS的委員非常勤快,在異步編程范式的標(biāo)準(zhǔn)制定也做的很好(以前我嘗試寫(xiě)NodeJS時(shí),幾個(gè)回調(diào)就直接把我勸退了),2009年誕生的NodeJS有點(diǎn)后來(lái)居上的意思。目前PHP只是趕上了協(xié)程,期待PHP的Async/Await語(yǔ)法糖的實(shí)現(xiàn)吧。

    PHP yield 使用注意事項(xiàng)

    一旦使用上 yield 后,就必須注意調(diào)用函數(shù)是,會(huì)得到函數(shù)結(jié)果,還是 生成器對(duì)象。PHP 不會(huì)自動(dòng)幫你區(qū)別,需要你手動(dòng)代碼判斷結(jié)果類(lèi)型—— if ($re instanceof Generator) {}, 如果你得到的是 生成器,但不希望去手動(dòng)調(diào)用 current() 去執(zhí)行它,那么在生成器前 使用 yield from 交給上游(框架)來(lái)解決。

    爆改 Workerman

    博客寫(xiě)到這,就開(kāi)始手癢癢了,看到Workerman框架,我在基礎(chǔ)上二開(kāi),使其能——同步編碼,異步執(zhí)行。

    代碼已放到:PaulXu-cn/CoWorkerman.git

    目前還是dev階段,大家喜歡可以先 體驗(yàn)一波。

    $ composer require paulxu-cn/co-workerman

    一個(gè)簡(jiǎn)單的單線(xiàn)程 TCP Server

    <?php// file: ./examples/example2/coWorkermanServer.php , 詳細(xì)代碼見(jiàn)github$worker = new CoWorker('tcp://0.0.0.0:8080');// 設(shè)置fork一個(gè)子進(jìn)程$worker->count = 1;$worker->onConnect = function (CoTcpConnection  $connection) {     try {         $conName = "{$connection->getRemoteIp()}:{$connection->getRemotePort()}";         echo PHP_EOL . "New Connection, {$conName} n";          $re = yield from $connection->readAsync(1024);         CoWorker::safeEcho('get request msg :' . $re . PHP_EOL );          yield from CoTimer::sleepAsync(1000 * 2);          $connection->send(json_encode(array('productId' => 12, 're' =>true)));          CoWorker::safeEcho('Response to :' . $conName . PHP_EOL . PHP_EOL);     } catch (ConnectionCloseException $e) {         CoWorker::safeEcho('Connection closed, ' . $e->getMessage() . PHP_EOL);     }};CoWorker::runAll();

    這里設(shè)置fork 一個(gè)worker線(xiàn)程,處理邏輯中帶有一個(gè)sleep() 2s的操作,依然不影響他同時(shí)響應(yīng)多個(gè)請(qǐng)求。

    啟動(dòng)測(cè)試程序

    ## 啟動(dòng)CoWorker服務(wù)$ php ./examples/example2/coWorkermanServer.php start## 啟動(dòng)請(qǐng)求線(xiàn)程$ php ./examples/example2/userClientFork.php

    運(yùn)行結(jié)果

    了解PHP yield的高級(jí)用法

    綠色箭頭——新的請(qǐng)求,紅色箭頭——響應(yīng)請(qǐng)求

    從結(jié)果上看到,這一個(gè)worker線(xiàn)程,在接收新的請(qǐng)求同時(shí),還在回復(fù)之前的請(qǐng)求,各個(gè)連接交錯(cuò)運(yùn)行。而我們的代碼呢,看樣子就是同步的,沒(méi)有回調(diào)。

    CoWorker購(gòu)物車(chē)服務(wù)

    好的,這里我們做幾個(gè)簡(jiǎn)單的微服務(wù)模擬實(shí)際應(yīng)用,這里模擬 用戶(hù)請(qǐng)求端,購(gòu)物車(chē)服務(wù),庫(kù)存服務(wù)產(chǎn)品服務(wù)。 模擬用戶(hù)請(qǐng)求加購(gòu)動(dòng)作,購(gòu)物車(chē)去分別請(qǐng)求 庫(kù)存,產(chǎn)品 校驗(yàn)用戶(hù)是否可以加購(gòu),并響應(yīng)客戶(hù)請(qǐng)求是否成功。

    代碼我就不貼了,太長(zhǎng)了,麻煩移步 CoWorkerman/example/example5/coCartServer.php

    運(yùn)行命令

    ## 啟動(dòng)庫(kù)存服務(wù)$ php ./examples/example5/otherServerFork.php 8081 inventory 1## 啟動(dòng)產(chǎn)品服務(wù)$ php ./examples/example5/otherServerFork.php  8082 product 2
    ## 啟動(dòng)CoWorker 購(gòu)物車(chē)服務(wù)$ php ./examples/example5/coCartServer.php start
    ## 用戶(hù)請(qǐng)求端$ php ./examples/example5/userClientFork.php

    運(yùn)行結(jié)果

    了解PHP yield的高級(jí)用法

    黃色箭頭——新的用戶(hù)請(qǐng)求,藍(lán)色箭頭——購(gòu)物車(chē)發(fā)起庫(kù)存,產(chǎn)品檢查請(qǐng)求,紅色箭頭——響應(yīng)用戶(hù)請(qǐng)求

    從圖中看到也是用1個(gè)線(xiàn)程服務(wù)多個(gè)連接,交錯(cuò)運(yùn)行。

    好的,那么PHP CoWorkerman 也能像 NodeJS 那樣用 Async/Await 那樣同步編碼,異步運(yùn)行了。

    快來(lái)試試這個(gè) CoWorkerman 吧:

    $ composer require paulxu-cn/co-workerman

    工作原理

    先上圖:了解PHP yield的高級(jí)用法

    圖的上部是Workerman 的工作泳道圖,圖下部是CoWorkerman的工作泳道圖。

    workerman內(nèi)的worker進(jìn)程遇到阻塞函數(shù)的處理方式時(shí),會(huì)等待IO返回,如果這個(gè)時(shí)候,又有了新的請(qǐng)求,那么閑的worker會(huì)競(jìng)爭(zhēng)到這個(gè)新的連接。

    我在上圖worker5中,描述了一個(gè)AsyncTCPConnection使用情況,woker內(nèi)發(fā)起了一個(gè)非阻塞請(qǐng)求,并注冊(cè)了回調(diào)函數(shù),然后程序繼續(xù)運(yùn)行到結(jié)束。當(dāng)異步請(qǐng)求響應(yīng)時(shí),就需要通過(guò)其他方式去響應(yīng)(如自己再發(fā)起一個(gè)請(qǐng)求告知請(qǐng)求方)。

    在下圖中CoWorkerman,也是多個(gè)Worker競(jìng)爭(zhēng)新的請(qǐng)求,當(dāng)worker1收到一個(gè)新的請(qǐng)求,會(huì)產(chǎn)生一個(gè)生成器,生成器內(nèi)發(fā)起異步請(qǐng)求,并注冊(cè)響應(yīng)回調(diào),請(qǐng)求響應(yīng)后,回到該生成器跳出(yield)的地方,繼續(xù)執(zhí)行代碼。

    發(fā)起異步請(qǐng)求,并注冊(cè)回調(diào)函數(shù),這些默認(rèn)工作CoWorkerman框架內(nèi)已做了,回調(diào)函數(shù)內(nèi)工作是:收到數(shù)據(jù),并發(fā)給 發(fā)起該請(qǐng)求的生成器。

    這例子中,通過(guò)調(diào)用 Promise:all() 發(fā)起多個(gè)請(qǐng)求,并監(jiān)聽(tīng)結(jié)果返回,待所有的響應(yīng)返回再繼續(xù)運(yùn)行生成器

    在程序yield跳出后,該worker就處于事件循環(huán)狀態(tài)($event->loop()),也就是多路監(jiān)聽(tīng):請(qǐng)求端口,第三方客戶(hù)端請(qǐng)求響應(yīng)端口。這個(gè)時(shí)候如果:

    1. 有新的請(qǐng)求來(lái),他和其他 worker 競(jìng)爭(zhēng)新的請(qǐng)求,如果競(jìng)爭(zhēng)到了,則該worker內(nèi)又產(chǎn)生一個(gè)新的 生成器。
    2. 客戶(hù)端有響應(yīng),則調(diào)用回調(diào)函數(shù)
    3. 客戶(hù)端都響應(yīng)了,繼續(xù)運(yùn)行 生成器程序。

    從1中,我們可假設(shè),如果就一個(gè) Worker,那么該 Worker 可以在上一個(gè)請(qǐng)求未完成情況下,繼續(xù)接受處理下一個(gè)請(qǐng)求。也就是 CoWorkerman 可以在單 Worker 下運(yùn)行,并發(fā)處理多個(gè)請(qǐng)求。

    當(dāng)然,這里也有個(gè)前提,單 Worker 模式內(nèi)不能運(yùn)行阻塞函數(shù),一旦阻塞,后續(xù)請(qǐng)求就會(huì)堵在網(wǎng)卡。所以,除非對(duì)自己的代碼非常了解,如果用到第三方庫(kù),那么我還是建議你在多 Worker 模式下運(yùn)行 CoWorkerman,阻塞時(shí),還有其他Worker兜住新請(qǐng)求。

    CoWorkerman 的意義

    1. 用同步的代碼,發(fā)起異步請(qǐng)求,多個(gè)請(qǐng)求可并發(fā),從IO串行等待,改為并行等待,減少無(wú)畏的等待時(shí)間。提高業(yè)務(wù)程序的效率同時(shí),不降低代碼可讀性。
    2. 在一個(gè)線(xiàn)程內(nèi)通過(guò)事件循環(huán),盡可能處理多個(gè)請(qǐng)求,緩解了一個(gè)請(qǐng)求一個(gè)線(xiàn)程帶來(lái)的頻繁線(xiàn)程切換,從核心上提高運(yùn)行效率。

    CoWorkerman 生態(tài)位

    適合處理純Socket請(qǐng)求的應(yīng)用,如Workerman Gateway,或者是 大前端 整合多個(gè)服務(wù)RPC結(jié)果, 綜合后返給前三頁(yè)這樣的場(chǎng)景.

    日志記錄是每個(gè)程序最基本需求,由于寫(xiě)文件函數(shù)是阻塞的,建議用消息隊(duì)列,或者redis隊(duì)列,更或者跳過(guò)Logstash直接丟Elasticsearch.

    CoWorkerman有他的局限性,也有他自己位置。

    總結(jié)

    好~PHP 協(xié)程編碼到 網(wǎng)絡(luò)異步編碼就到此結(jié)束了,如果看到本文章有很多疑惑,歡迎留言提問(wèn),如果是 yield 語(yǔ)法不太記得,可以先讀一讀這個(gè)系列前幾篇文章復(fù)習(xí)一下。

    如果行,請(qǐng)三連。CoWorkerman 謝謝!

    了解PHP yield的高級(jí)用法

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)