關(guān)于 PHP 8.1 的 Fiber RFC

最新的 PHP 8.1 增加了一個(gè) Fiber 的提案,最近討論的比較多。有不少好事者拿來(lái)說(shuō)事兒,說(shuō)是 “Fiber 進(jìn)入內(nèi)核之后,Swoole 的使用者就大幅減少“

實(shí)際上 Fiber 擴(kuò)展進(jìn)入內(nèi)核后,由于它是一個(gè)非常底層的 API ,并不是直接可以使用的技術(shù),不會(huì)對(duì) Swoole 產(chǎn)生影響。真正和 Swoole 競(jìng)爭(zhēng)的是應(yīng)該是 Amphp 、ReactPHP 。Fiber 反而對(duì) Swoole 是有好處的,PHP 內(nèi)核開發(fā)者維護(hù)了協(xié)程切換的全局狀態(tài)列表,Swoole PHPCoroutine 這部分的代碼實(shí)現(xiàn)就變簡(jiǎn)單了。另外,其他擴(kuò)展也會(huì)注意到協(xié)程的存在,使用 C 全局變量或棧上內(nèi)存時(shí)考慮到協(xié)程切換的可能性,避免出現(xiàn) Crash。ext-fiber 合并進(jìn)來(lái)之后,也應(yīng)標(biāo)記為 alpha 狀態(tài),一些特殊情況能會(huì)引起崩潰,需要比較長(zhǎng)的時(shí)間去收集解決這些問(wèn)題。

最近這幾年即便官方連續(xù)出了很多個(gè)大版本,PHP 還是一直是在走下坡路。有許多 PHP 開發(fā)者說(shuō)是因?yàn)?PHP 性能不行,沒(méi)有 JIT。于是 PHP8.0 加入了 JIT。還有人說(shuō) PHP 沒(méi)有協(xié)程,所以 PHP8.1 要加入 Fiber。馬上就會(huì)有人說(shuō) PHP 缺少多線程,按照現(xiàn)在這個(gè)節(jié)奏,可以預(yù)見未來(lái)有可能 PHP 的多線程擴(kuò)展 parallels 也會(huì)合并到內(nèi)核。PHP8 還加入了一個(gè) FFI 模塊,甚至可以直接使用 PHP 調(diào)用 C 庫(kù)。

可是真的加入如此多的能力,PHP 就得到很大的改變了嗎?

你們想要的 Fiber 是這樣的:


1.png

實(shí)際上 PHP 8.1 Fiber 是這樣的:


2.png

動(dòng)態(tài)語(yǔ)言中除了 PHP 之外,Python、Ruby、Lua 在很早就有協(xié)程支持了,但實(shí)際上這些編程語(yǔ)言在協(xié)程并發(fā)編程方面并沒(méi)有多出色。真正將協(xié)程技術(shù)發(fā)揚(yáng)光大的是 Golang ,為什么 Golang 在協(xié)程編程方面的如此成功?這是因?yàn)樗峁┝送暾?、成體系的一整套技術(shù)方案,從語(yǔ)言設(shè)計(jì)到編譯器、協(xié)程調(diào)度器、標(biāo)準(zhǔn)庫(kù)、調(diào)試器,這才是工業(yè)級(jí)的技術(shù)。在多線程技術(shù)方向,很多編程語(yǔ)言都有多線程支持,但真正被廣泛使用、達(dá)到工業(yè)級(jí)水平的多線程系統(tǒng)只有 Java 。在 PHP 中真正能達(dá)到工業(yè)級(jí)水平的技術(shù)也就是 Apache+mod_php 和 PHP-FPM 。

協(xié)程的技術(shù)也是一樣,PHP 開發(fā)者想要從傳統(tǒng)的 LAMP/LNMP 短生命周期、串行編程的模式轉(zhuǎn)型到 CSP 協(xié)程+通道并發(fā)編程,目前暫時(shí)也只有 Swoole 是相對(duì)來(lái)說(shuō)最成熟的方案。用戶真正需要的是一種完整的、系統(tǒng)性、成體系、簡(jiǎn)單易用、可靠的一整套技術(shù)方案。

PHP 8.1 加入 Fiber 我認(rèn)為是一個(gè)倉(cāng)促的決定。不如系統(tǒng)性地設(shè)計(jì)一下,從這些7個(gè)方面考慮:

EventLoop API
協(xié)程(對(duì)應(yīng) ext-fiber)
IO 調(diào)度器(Socket/FileSystem/ChildProcess/Signal/Timer/Stdout/Stdin)
CPU 調(diào)度器
現(xiàn)有同步阻塞 IO 擴(kuò)展(redis、curl、php_stream、sockets、mysqli、pdo_mysql 等)和內(nèi)置函數(shù)(sleep、shell_exec、sleep、gethostbyname 等)如何實(shí)現(xiàn)支持協(xié)程,變成異步非阻塞模式
協(xié)程通信(channel)
服務(wù)器:實(shí)現(xiàn) PHP-FPM 協(xié)程版,或者提供一個(gè)新的協(xié)程 HttpServer

事件循環(huán)

EventLoop 是協(xié)程實(shí)現(xiàn)中最核心的基礎(chǔ)設(shè)施,這里不是指具體實(shí)現(xiàn),C 層面 select/epoll/poll ,PHP 層面 stream_select 或者 libevent/libuv/event 擴(kuò)展都可以實(shí)現(xiàn),如果 ZendVM 底層提供了 EventLoop,那么不同的框架、不同的庫(kù)可以在同一個(gè) Loop 中,協(xié)程調(diào)度器也可以構(gòu)建在此之上。如果沒(méi)有統(tǒng)一的 EventLoop 的基礎(chǔ)設(shè)施,amphp 、 reactphp 等框架都需要各自實(shí)現(xiàn),意味著你在使用 amphp 的程序時(shí),無(wú)法使用 reactphp 實(shí)現(xiàn)的任何類庫(kù)。

Node.js、Golang、Swoole 底層都有一個(gè)全局的 EventLoop,所有 IO 行為都會(huì)被注冊(cè)到 EventLoop 中,事件觸發(fā)后執(zhí)行 callback 或者調(diào)度協(xié)程。

阻塞 IO 函數(shù)

PHP 提供的很多 IO 操作函數(shù)都是阻塞的,如果在協(xié)程中發(fā)生阻塞,就會(huì)導(dǎo)致并發(fā)失效。退化成和普通 PHP-FPM 一樣的串行模式。協(xié)程實(shí)現(xiàn)中必須要考慮到如何解決這個(gè)問(wèn)題。

Amphp 和 ReactPHP 目前(2021年)采用的實(shí)現(xiàn)方式,是使用 PHP 代碼實(shí)現(xiàn)基于協(xié)程的異步非阻塞 IO 版本,在 2018 年之前 Swoole 也是采用這個(gè)模式。這樣做最大的問(wèn)題是,

1.成本太高,無(wú)法復(fù)用 PHP 生態(tài),重復(fù)造輪子,需要重新實(shí)現(xiàn) Redis、MySQL、CURL、Http2、WebSocket、Kafka 等大量網(wǎng)絡(luò) IO 庫(kù)
2.質(zhì)量不高,不像同步阻塞的版本經(jīng)過(guò)大規(guī)模驗(yàn)證
3.兼容性差,如果用戶使用了一個(gè)第三方庫(kù),其中包含了阻塞 IO 的客戶端調(diào)用,就前功盡棄了
4.學(xué)習(xí)成本高,用戶需要學(xué)習(xí)一套全新的 API ,這對(duì) PHP 開發(fā)者非常不友好
5.PHP 實(shí)現(xiàn)的版本可能還會(huì)存在性能問(wèn)題,在 Swoole 中由于是使用 C 實(shí)現(xiàn)的不存在這一點(diǎn)

所以 Swoole 在 4.1 版本(2018年)開始采用了全新的實(shí)現(xiàn)方式,會(huì) Hook 掉 PHP 擴(kuò)展中的函數(shù)指針,通過(guò)很少的工作量就徹底解決了這個(gè)問(wèn)題。PHP 開發(fā)者直接使用同步阻塞客戶端的 API 即可,底層會(huì)自動(dòng)替換為非阻塞的協(xié)程版本。比如下面的代碼

3.png

Swoole 會(huì)替換 PHP 內(nèi)置的 sleep 和 file_get_contents 函數(shù),變成協(xié)程版本,上面的程序就變成了完全并發(fā)的了,對(duì)用戶來(lái)說(shuō)是無(wú)感知的。

CPU 調(diào)度器

由于 PHP 是動(dòng)態(tài)解釋執(zhí)行的編程語(yǔ)言,在實(shí)現(xiàn)協(xié)程 CPU 調(diào)度器方面比 Golang 有優(yōu)勢(shì)。Golang 需要在編譯器內(nèi)做很多工作,控制單個(gè)協(xié)程占用的 CPU 時(shí)間,避免幾個(gè)協(xié)程耗盡 CPU 資源。PHP 可以在 VM 層面直接實(shí)現(xiàn)中斷,精準(zhǔn)控制每個(gè)協(xié)程最大可執(zhí)行的時(shí)間。

Golang 使用 GPM 模型解決了這個(gè)問(wèn)題,如果一部分協(xié)程持續(xù)占用 CPU ,調(diào)度器會(huì)創(chuàng)建更多 Thread 執(zhí)行新的任務(wù),退化為操作系統(tǒng)調(diào)度。PHP 由于不支持多線程,暫時(shí)無(wú)法實(shí)現(xiàn) GPM 模型,目前 Swoole 所采作用的 VM 中斷調(diào)度實(shí)現(xiàn)是最優(yōu)解

在 Swoole 的實(shí)現(xiàn)中,底層創(chuàng)建了一個(gè)中斷線程,每 5ms 會(huì)產(chǎn)生一個(gè)中斷信號(hào),在中斷函數(shù)中判斷當(dāng)前協(xié)程執(zhí)行的時(shí)長(zhǎng),如果超過(guò)了規(guī)定的 10ms 最大執(zhí)行時(shí)間,會(huì)自動(dòng)讓出 CPU 切換至其他可執(zhí)行的協(xié)程。

4.png

以上程序會(huì)交替執(zhí)行,每個(gè)協(xié)程最大執(zhí)行時(shí)間不超過(guò) 10ms

我認(rèn)為正確的方式

創(chuàng)建多個(gè) RFC ,把這些問(wèn)題討論清楚,在 PHP9 版本中提供完整的協(xié)程方案實(shí)現(xiàn)。不求做到 Golang 的程度,至少要能達(dá)到生產(chǎn)可用。這樣 PHP 才會(huì)有大的改變。不過(guò)這可能就真要取代 Swoole 了 [哭笑]。

再介紹一下 Swoole 現(xiàn)在做到什么程度:

完整的協(xié)程+通道實(shí)現(xiàn)
提供了 CPU 調(diào)度器,即使是密集計(jì)算的程序,也可以使用協(xié)程,調(diào)度器會(huì)按照10ms時(shí)間片切換協(xié)程
支持絕大部分 PHP 的常用擴(kuò)展和內(nèi)置函數(shù),LAMP 時(shí)代的代碼可以不用修改直接 copy 到協(xié)程里運(yùn)行,而且是異步非阻塞的方式,是真正的并發(fā),我認(rèn)為這才是黑科技,PHP 協(xié)程方案的關(guān)鍵技術(shù)
curl 擴(kuò)展也可以協(xié)程化,包括 curl 和 curl_multi,guzzle 可以直接用,騰訊云、阿里云的 PHP SDK 可以直接在協(xié)程中使用
提供了 PGSQL 協(xié)程實(shí)現(xiàn),基于 pgsql 官方 C 庫(kù)的異步 API 實(shí)現(xiàn)
提供了 ZooKeeper 協(xié)程實(shí)現(xiàn),是基于官方的 C 庫(kù) 插入了 Hook 代碼實(shí)現(xiàn)
Kafka 協(xié)程庫(kù)的實(shí)現(xiàn)
支持協(xié)程的新一代調(diào)試器:yasd
支持 PHP7.2-8.0 所有版本,ext-fiber 只支持 8.0 以上版本

PHP 作為一個(gè)社區(qū)驅(qū)動(dòng)的開源項(xiàng)目,背后沒(méi)有商業(yè)公司支持,沒(méi)有 Golang、Java、Node.js(v8) 這樣充足的研發(fā)資源投入,需要依賴全世界各地的貢獻(xiàn)者提交代碼,在產(chǎn)品化方面還是做的不夠好。國(guó)內(nèi)有一些人一直在 diss Swoole 有商業(yè)公司,但正是因?yàn)橛猩虡I(yè)收入,才保證了我們?cè)?Swoole 開源項(xiàng)目研發(fā)上的連續(xù)性,在產(chǎn)品化方面也會(huì)做的更好。

我對(duì) Fiber 的擔(dān)憂

Fiber 集成到 PHP 中之后,會(huì)有很多 PHP 的框架或者類庫(kù)創(chuàng)建自己的協(xié)程方案,由于 PHP 只提供了 Coroutine Context 的實(shí)現(xiàn),其他幾個(gè)方面并沒(méi)有提供,在 PHP 生態(tài)中將會(huì)出現(xiàn)很多流派的 EventLoop、AsyncIO 、NetworkClient 多種多樣的實(shí)現(xiàn)。就拿 sleep 函數(shù)來(lái)說(shuō),現(xiàn)在 Amphp 和 ReactPHP 分別叫做 amp\delay 和 react\sleep 。

沒(méi)有統(tǒng)一的標(biāo)準(zhǔn),意味著社區(qū)的高度分裂。一旦確定了方向,技術(shù)的發(fā)展演進(jìn)是非??斓模鄻有允且患檬?,但也會(huì)帶來(lái)更多新問(wèn)題,再想統(tǒng)一是很困難的一件事情。即便是 Symfony、Laravel 這樣處于頂端的 PHP 框架,也不具備能夠 100% 覆蓋整個(gè) PHP 生態(tài)的能力,這將走向失控。

基于 Swoole/Swow 的方案,實(shí)際上依舊是 PHP 原先的生態(tài),大家使用的依然是最熟悉的那些 PHP 函數(shù)和庫(kù),異步編程和同步阻塞 IO 編程的生態(tài)是一致的。比如在 Swoole 協(xié)程中可以直接使用阿里云、騰訊云、AWS 提供的 SDK,F(xiàn)iber 生態(tài)下情況就會(huì)比較復(fù)雜。

Swow 是一個(gè)從 Swoole 項(xiàng)目中剝離與協(xié)程無(wú)關(guān)特性,使用 Swoole 協(xié)程設(shè)計(jì)方案的全新實(shí)現(xiàn),與 php-src 保持一致使用了 C 語(yǔ)言實(shí)現(xiàn),目前正在準(zhǔn)備 RFC 提案,貢獻(xiàn)到 PHP 內(nèi)核中

Swoole 與 Fiber 的差別

Fiber 只是協(xié)程 Context 管理的一種實(shí)現(xiàn),更像是 Generator 的升級(jí)版
Swoole 是完整的協(xié)程 Runtime & Framework,更像是 Golang

Swoole 是否會(huì)使用 ext-fiber ?

暫時(shí)不會(huì)。有兩方面的原因:1. Swoole 的實(shí)現(xiàn)是雙層協(xié)程設(shè)計(jì),底層是 C 協(xié)程,上層是 PHP 協(xié)程。而 Fiber 的實(shí)現(xiàn)耦合在一起的。Swoole 是內(nèi)核協(xié)程化設(shè)計(jì),在 core 層面對(duì)協(xié)程操作進(jìn)行了封裝,外層只需要調(diào)用 API 即可,不需要關(guān)心發(fā)生阻塞 IO 時(shí)協(xié)程如何切換,PHP 層實(shí)際上只是 wrapper ,2. Fiber 是以擴(kuò)展方式加入 PHP 內(nèi)核的,并不是 ZendAPI,地位等同于 curl/mysql 等擴(kuò)展庫(kù)。在 PHP 中擴(kuò)展依賴管理做的很糟糕。處理不好容易出現(xiàn)找不到 符號(hào)(symbol not found),而 Swoole 同樣也是 PHP 的一個(gè)擴(kuò)展,它與 ext-fiber 是平級(jí)關(guān)系,協(xié)程是 Swoole 的核心部分,不太好依賴另外一個(gè)庫(kù)的實(shí)現(xiàn)。

當(dāng)然 Swoole 會(huì)對(duì)齊 ext-fiber 在 PHP 協(xié)程切換部分的代碼,保證一致性。由于 PHP 暫時(shí)還未提供 EventLoop 的基礎(chǔ)設(shè)施,Swoole 擴(kuò)展提供的功能和其他基于 ext-fiber 擴(kuò)展實(shí)現(xiàn)的 PHP 協(xié)程類庫(kù),不在同一個(gè) Loop 中,也無(wú)法實(shí)現(xiàn)共存。

創(chuàng)建 C 協(xié)程


5.png

創(chuàng)建 PHP 協(xié)程


6.png

在 PHP 代碼中創(chuàng)建協(xié)程


7.png

我們也會(huì)持續(xù)關(guān)注 ext-fiber ,在未來(lái)某個(gè)節(jié)點(diǎn),ext-fiber 足夠成熟穩(wěn)定,并且遷移到 ZendAPI 中時(shí),Swoole 也會(huì)考慮使用 ext-fiber

點(diǎn)關(guān)注,不迷路

好了各位,以上就是這篇文章的全部?jī)?nèi)容了,能看到這里的人呀,都是人才。之前說(shuō)過(guò),PHP方面的技術(shù)點(diǎn)很多,也是因?yàn)樘嗔?,?shí)在是寫不過(guò)來(lái),寫過(guò)來(lái)了大家也不會(huì)看的太多,所以我這里把它整理成了PDF和文檔,如果有需要的可以點(diǎn)這里進(jìn)階PHP月薪30k>>>架構(gòu)師成長(zhǎng)路線【視頻、面試文檔免費(fèi)獲取】

24020938-0e1d4138a0a22218.png

24020938-7cffd24782dd56f9.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容