開篇
剛開始接觸PHP
的yield
的時候,感覺,yield
是什麼黑科技,百度一下:yield
——協程,生成器。很多文章都在講 Iterator
,Generater
, 蛤~,這東西是 PHP 迭代器的一個補充。再翻幾頁,就是Go 協程
。我出於好奇點開看了下Go 協程
, 裡面都是並發
,線程
,管道通訊
這類字眼, wc,nb, 這tm才是黑科技啊,再回來看PHP
,分分鐘想轉Go
。
相關學習推薦:PHP程式設計從入門到精通
#yield 語法加入PHP
yield
語法是在版本5.5加入PHP
的,配合迭代器使用,功能上就是流程控制
程式碼,和goto
,return
類似。
以下就是官方提供的yield 小例子,透過執行結果,我們可分析當程式碼執行到yield $i
時,他會進行return $i
,待echo "$value\n"
後, goto
for ($i = 1; $i , 對! PHP 的 yield 就是一個能出能進的語法。在z程式碼中七進七出,把 <code>$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 "$value\n";}// output12...67
我們遇到了什麼問題
寫程式碼就是解決問題。我們來看看他們遇到了什麼問題:php官方呢,需要言簡意賅地把yield介紹給大家。一部分網友呢,需要在有限的資源內完成大文件操作。而我們的鳥哥。面對的一群對當下yield的教程停留於初級而不滿意的phper,就以一個任務調度器作為例子,給大家講了一種yield
高級用法。
php.net:生成器語法,
PHP如何讀取大文件,
風雪之隅:在PHP中使用協程實現多任務調度.
提出問題,再用yield
來解答,看到以上答案,我覺得呢,這PHP協程不過如此(和Go協程
相比 )。
有句話-一個好問題比答案更重要
,目前廣大網友還沒有給yield提出更好,更困難的問題。
yield
這個進進出出的語法,很多舉例都是再讓yield做迭代器啊,或者利用低內存讀取超大文本的Excel
,csv
什麼的,再進階就是用它實作一個簡單的任務調度器,而且這個調度器,一看程式碼都差不多。
我來出題
正如一個好的問題,比答案更有價值
- 用PHP實作一個Socket Server,他能接收請求,並回傳Server的時間。
好,這是第一個問題,鋪墊。官方答案
- 在原來的程式碼上,我們加個需求,該Socket Server 處理請求時,依賴其他 Socket Server,還需要有 Client 功能。也就是他能接收請求,向其它Server發起請求。
這是第二個問題,也是鋪墊。
- 原來的Socket Server同一時間只能服務一個客戶,希望能實現一個
非阻塞I/O
Socket Server, 這個Server 內有Socket Client 功能,支援並發處理收到的請求,和主動發起的請求。要求不用多執行緒,多行程。
這個問題,還是鋪墊,這幾個問題很乾,大家可以想一想,2,3題的答案,都放在一個腳本裡了:nio_server.php
#以上這段程式碼,我列舉了一個具體的業務,就是用戶請求購物車加購動作, 而購物車服務呢,又需要和產品服務,庫存服務,優惠服務交互,來驗證加購動作。有同步,非同步方式請求,並做比較。
後續還有很多程式碼,我都放gitee連結了。使用方法,見readme.md
- 最後一個問題:在PHP中,用同步寫程式碼,程式呢非同步執行?需要怎麼調整程式碼。
提示:這個和 PHP
的 yield
語法有關。
再提示:yield
語法特徵是什麼,進進出出!
看著我們的程式碼,同步, 非同步,進進出出 你想到了什麼?
看到程式碼,同步處理模式下,這三個函數checkInventory
checkProduct
checkPromo
時,發起請求,並依序等待返回的結果,這三個函數執行後,再回應客戶請求。
异步处理模式下,这三个函数发起请求完毕后,代码就跳出循环了,然后是在select()
下的一个代码分支中接收请求, 并收集结果。每次收到结果后判断是否完成,完成则响应客户端。
那么能不能这样:在异步处理的流程中,当 Server
收到 自己发起的 client
有数据响应后,代码跳到 nio_server.php 的 247行呢,这样我们的收到请求校验相关的代码就能放到这里,编码能就是同步,容易理解。不然,client
的响应处理放在 280 行以后,不通过抓包,真的很难理解,执行了第 247 行代码后,紧接着是从 280 行开始的。
诶~这里是不是有 进进出出 那种感觉了~ 代码从 247 行出去,开始监听发出 Client
响应,收到返回数据,带着数据再回到 247 行,继续进行逻辑校验,综合结果后,再响应给客户端。
用yield来解决问题
基于 yield 实现的,同步编码,"异步"I/O
的 Socket Server
就实现了。代码。
这里 “异步” 打了引号,大佬别扣这个字眼了。 该是
非阻塞I/O
不等大家的答案了,先上我的结果代码吧,代码呢都放在这个目录下了。
gitee https://gitee.com/xupaul/PHP-generator-yield-Demo/tree/master/yield-socket
运行测试代码
clone 代码到本地后,需要拉起4个 command 命令程序:
拉起3个第三方服务
## 启动一个处理耗时2s的库存服务$ php ./other_server.php 8081 inventory 2## 启动一个处理耗时4s的产品服务$ php ./other_server.php 8082 product 4## 监听8083端口,处理一个请求 耗时6s的 promo 服务$ php ./other_server.php 8083 promo 6
启动购物车服务
## 启动一个非阻塞购物车服务$ php ./async_cart_server.php ## 或者启动一个一般购物车服务$ php ./cart_server.php
发起用户请求
$ php ./user_client.php
运行结果呢如下,通过执行的时间日志,可得这三个请求是并发发起的,不是阻塞通讯。
在看我们的代码,三个函数,发起socket
请求,没有设置callback
,而是通过yield from
接收了三个socket
的返回结果。
也就是达到了,同步编码,异步执行的效果。
运行结果
非阻塞模式
client 端日志:
通过以上 起始时间
和 结束时间
,就看到这三个请求耗时总共就6s,也就按照耗时最长的promo服务的耗时来的。也就是说三个第三方请求都是并发进行的。
cart server 端日志:
而 cart 打印的日志,可以看到三个请求一并发起,并一起等待结果返回。达到非阻塞并发请求的效果。
阻塞模式
client 端日志:
以上是阻塞方式请求,可以看到耗时 12s。也就是三个服务加起来的耗时。
cart server 端日志:
cart 服务,依次阻塞方式请求第三方服务,顺序执行完毕后,共耗时12s,当然如果第一个,获第二个服务报错的话,会提前结束这个检查。会节约一点时间。
工作原理
这里就是用到了 yield
的工作特点——进进出出,在发起非阻塞socket
请求后,不是阻塞方式等待socket响应,而是使用yield
跳出当前执行生成器,等待有socket响应后,在调用生成器的send
方法回到发起socket
请求的函数内,在 yield from Async::all()
接收数据响应数据搜集完毕后,返回。
和Golang比一比
考虑到网速原因,我这就放上一个国内教程链接:Go 并发 教程
php
的协程是真协程,而Go
是披着协程外衣的轻量化线程(“协程”里,都玩上“锁”了,这就是线程)。
我个人偏爱,协程的,觉得线程的调度有一定随机性,因此需要锁机制来保证程序的正确,带来了额外开销。协程的调度(换入换出)交给了用户,保证了一段代码执行连续性(当然进程级上,还是会有换入换出的,除非是跨进程的资源访问,或者跨机器的资源访问,这时,就要用到分布式锁了,这里不展开讨论),同步编码,异步执行,只需要考虑那个哪个方法会有IO交互会协程跳出即可。
和NodeJS比划一下
Javascript 和 PHP 两个脚本语言有很多相似的地方,弱类型,动态对象,单线程,在Web领域生态丰富。不同的是,Javascript
在浏览器端一开始就是异步的(如果js发起网络请求只能同步进行,那么你的网页渲染线程会卡住),例如Ajax
,setTimeout
,setInterval
,这些都是异步+回调的方式工作。
基于V8引擎而诞生的NodeJS
,天生就是异步的,在提供高性能网络服务有很大的优势,不过它的IO编码范式
么。。。刚开始是 回调——毁掉地狱,后来有了Promise——屏幕竖起来看,以及Generator
——遇事不绝yield
一下吧,到现在的Async/Await
——语法糖?真香!
可以说JS的委员非常勤快,在异步编程范式的标准制定也做的很好(以前我尝试写NodeJS
时,几个回调就直接把我劝退了),2009年诞生的NodeJS
有点后来居上的意思。目前PHP
只是赶上了协程,期待PHP的Async/Await
语法糖的实现吧。
PHP yield 使用注意事项
一旦使用上 yield 后,就必须注意调用函数是,会得到函数结果,还是 生成器对象。PHP 不会自动帮你区别,需要你手动代码判断结果类型—— if ($re instanceof \Generator) {}
, 如果你得到的是 生成器,但不希望去手动调用 current() 去执行它,那么在生成器前 使用 yield from 交给上游(框架)来解决。
爆改 Workerman
博客写到这,就开始手痒痒了,看到Workerman框架,我在基础上二开,使其能——同步编码,异步执行。
代码已放到:PaulXu-cn/CoWorkerman.git
目前还是dev阶段,大家喜欢可以先 体验一波。
$ composer require paulxu-cn/co-workerman
一个简单的单线程 TCP Server
<?php// file: ./examples/example2/coWorkermanServer.php , 详细代码见github$worker = new CoWorker('tcp://0.0.0.0:8080');// 设置fork一个子进程$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();
这里设置fork 一个worker
线程,处理逻辑中带有一个sleep()
2s
的操作,依然不影响他同时响应多个请求。
启动测试程序
## 启动CoWorker服务$ php ./examples/example2/coWorkermanServer.php start## 启动请求线程$ php ./examples/example2/userClientFork.php
运行结果
绿色箭头——新的请求,红色箭头——响应请求
从结果上看到,这一个worker线程,在接收新的请求同时,还在回复之前的请求,各个连接交错运行。而我们的代码呢,看样子就是同步的,没有回调。
CoWorker购物车服务
好的,这里我们做几个简单的微服务模拟实际应用,这里模拟 用户请求端
,购物车服务
,库存服务
,产品服务
。 模拟用户请求加购动作,购物车去分别请求 库存,产品 校验用户是否可以加购,并响应客户请求是否成功。
代码我就不贴了,太长了,麻烦移步 CoWorkerman/example/example5/coCartServer.php
运行命令
## 启动库存服务$ php ./examples/example5/otherServerFork.php 8081 inventory 1## 启动产品服务$ php ./examples/example5/otherServerFork.php 8082 product 2
## 启动CoWorker 购物车服务$ php ./examples/example5/coCartServer.php start
## 用户请求端$ php ./examples/example5/userClientFork.php
运行结果
黄色箭头——新的用户请求,蓝色箭头——购物车发起库存,产品检查请求,红色箭头——响应用户请求
从图中看到也是用1个线程服务多个连接,交错运行。
好的,那么PHP CoWorkerman
也能像 NodeJS
那样用 Async/Await
那样同步编码,异步运行了。
快来试试这个 CoWorkerman 吧:
$ composer require paulxu-cn/co-workerman
工作原理
先上图:
圖的上部是Workerman 的工作泳道圖,圖下部則是CoWorkerman的工作泳道圖。
workerman
內的worker進程
遇到阻塞函數的處理方式時,會等待IO返回,如果這個時候,又有了新的請求,那麼閒的worker會競爭到這個新的連結。
我在上圖worker5中,描述了一個AsyncTCPConnection
使用情況,woker內發起了一個非阻塞請求,並註冊了回呼函數,然後程式繼續運行到結束。當非同步請求回應時,就需要透過其他方式去回應(如自己再發起一個請求告知請求方)。
在下圖中CoWorkerman
,也是多個Worker競爭新的請求,當worker1收到一個新的請求,會產生一個生成器,生成器內發起異步請求,並註冊回應回調,請求回應後,回到該生成器跳出(yield
)的地方,繼續執行程式碼。
發起非同步請求,並註冊回調函數,這些預設工作
CoWorkerman
框架內已做了,回呼函數內工作是:收到數據,並發給發起該請求的生成器。
這範例中,透過呼叫Promise:all() 發起多個請求,並監聽結果返回,待所有的回應返回再繼續運行生成器
在程式yield
跳出後,該worker就處於事件循環狀態($event->loop()
),也就是多路監聽:請求端口,第三方客戶端請求響應埠。這時候如果:
- 有新的請求來,他和其他
worker
競爭新的請求,如果競爭到了,則該worker內又產生一個新的 生成器。 - 客戶端有回應,則呼叫回呼函數
- 客戶端都回應了,繼續執行 生成器程式。
從1中,我們可假設,如果就一個Worker
,那麼該Worker
可以在上一個請求未完成情況下,繼續接受處理下一個請求。也就是 CoWorkerman
可以在單一 Worker
下運行,並發處理多個請求。
當然,這裡也有個前提,單
Worker
模式內不能運行阻塞函數,一旦阻塞,後續請求就會堵在網卡。所以,除非對自己的程式碼非常了解,如果用到第三方函式庫,那麼我還是建議你在多Worker
模式下運行CoWorkerman
,阻塞時,還有其他Worker
兜住新請求。
CoWorkerman 的意義
- 用同步的程式碼,發起非同步請求,多個請求可並發,從IO串行等待,改為並行等待,減少無畏的等待時間。提高業務程序的效率同時,不降低程式碼可讀性。
- 在一個執行緒內通過事件循環,盡可能處理多個請求,緩解了一個請求一個執行緒帶來的頻繁執行緒切換,從核心上提高運行效率。
CoWorkerman 生態位
適合處理純Socket
請求的應用,如Workerman Gateway
,或是大前端
整合多個服務RPC
結果, 綜合後返給前三頁
這樣的場景.
日誌記錄是每個程式最基本需求,由於寫入檔案函數是阻塞的,建議用訊息佇列,或是redis佇列,更或跳過
Logstash
直接丟#Elasticsearch
.
CoWorkerman有他的局限性,也有他自己位置。
總結
好~PHP 協程編碼到網路非同步編碼就到此結束了,如果看到這篇文章有很多疑惑,歡迎留言提問,如果是yield
文法不太記得,可以先讀一讀這個系列前幾篇文章複習一下。
如果行,請三連。 CoWorkerman
謝謝!
以上是了解PHP yield的高階用法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

Fibers在PHP8.1中引入,提升了並發處理能力。 1)Fibers是一種輕量級的並發模型,類似於協程。 2)它們允許開發者手動控制任務的執行流,適合處理I/O密集型任務。 3)使用Fibers可以編寫更高效、響應性更強的代碼。

PHP社區提供了豐富的資源和支持,幫助開發者成長。 1)資源包括官方文檔、教程、博客和開源項目如Laravel和Symfony。 2)支持可以通過StackOverflow、Reddit和Slack頻道獲得。 3)開發動態可以通過關注RFC了解。 4)融入社區可以通過積極參與、貢獻代碼和學習分享來實現。

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP不是在消亡,而是在不斷適應和進化。 1)PHP從1994年起經歷多次版本迭代,適應新技術趨勢。 2)目前廣泛應用於電子商務、內容管理系統等領域。 3)PHP8引入JIT編譯器等功能,提升性能和現代化。 4)使用OPcache和遵循PSR-12標準可優化性能和代碼質量。

PHP的未來將通過適應新技術趨勢和引入創新特性來實現:1)適應云計算、容器化和微服務架構,支持Docker和Kubernetes;2)引入JIT編譯器和枚舉類型,提升性能和數據處理效率;3)持續優化性能和推廣最佳實踐。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SublimeText3 Linux新版
SublimeText3 Linux最新版

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Atom編輯器mac版下載
最受歡迎的的開源編輯器

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。