讓我們認(rèn)識(shí)一下PHP非阻塞并發(fā)框架Amp
什么是異步編程?
當(dāng)使用PHP編寫(xiě)的應(yīng)用程序I/O任務(wù)時(shí),程序會(huì)在執(zhí)行某個(gè)任務(wù)之前,一定要等待之前的任務(wù)完成,這時(shí)CPU會(huì)有很多時(shí)間處于空閑狀態(tài),這不僅會(huì)降低應(yīng)用程序性能,還會(huì)降低硬件利用率。比如,當(dāng)程序需要從數(shù)據(jù)庫(kù)中讀取大量的數(shù)據(jù)時(shí),由于需要等待I/O操作完成,程序的執(zhí)行速度會(huì)非常緩慢。
因此,我們通過(guò)事件庫(kù),在程序執(zhí)行的過(guò)程中,不需要等待某個(gè)任務(wù)完成才能執(zhí)行下一個(gè)任務(wù)。這種編程模式可以極大地提高程序的效率和響應(yīng)速度,尤其在處理復(fù)雜的I/O操作時(shí)表現(xiàn)得更為出色,而這就是異步編程。
Amphp
Amphp/Amp 是一個(gè)輕量級(jí)、高效的PHP異步庫(kù),為開(kāi)發(fā)人員提供了一種處理I/O密集型任務(wù)和網(wǎng)絡(luò)操作的新方式。它基于coroutine(協(xié)程)模型,讓你能夠編寫(xiě)出并發(fā)執(zhí)行的任務(wù),從而最大化服務(wù)器資源利用率,提高應(yīng)用性能。
核心技術(shù)
Amp的核心是它的事件循環(huán)和coroutine(協(xié)程)支持。事件循環(huán)監(jiān)聽(tīng)系統(tǒng)級(jí)別的事件,如文件描述符的狀態(tài)變化或定時(shí)器觸發(fā),而coroutine則允許代碼在不阻塞主線(xiàn)程的情況下進(jìn)行暫停和恢復(fù)。這種設(shè)計(jì)使得開(kāi)發(fā)者可以以同步代碼的風(fēng)格編寫(xiě)異步程序,降低了異步編程的學(xué)習(xí)曲線(xiàn)。
此外,Amp還提供了Promise/Try機(jī)制,這是一套處理異步操作成功與失敗的工具。通過(guò)Promise對(duì)象,你可以輕松地鏈?zhǔn)教幚懋惒讲僮鳎?yōu)雅地處理錯(cuò)誤。
應(yīng)用場(chǎng)景
- 網(wǎng)絡(luò)I/O: Amp非常適合處理大量HTTP請(qǐng)求、TCP連接或其他網(wǎng)絡(luò)通信,如:Websocket。它可以并行處理這些連接,顯著提升Web服務(wù)的吞吐量。
- 數(shù)據(jù)庫(kù)交互: 異步數(shù)據(jù)庫(kù)操作可以大大提高數(shù)據(jù)讀取和寫(xiě)入的速度,尤其是在需要處理多個(gè)查詢(xún)時(shí)。
- 文件系統(tǒng)操作: 讀寫(xiě)大文件或者遍歷大量目錄時(shí),Amp可以通過(guò)異步操作避免阻塞主線(xiàn)程。
- 后臺(tái)任務(wù): 對(duì)于耗時(shí)較長(zhǎng)的后臺(tái)任務(wù),如數(shù)據(jù)處理、爬蟲(chóng)或批量更新,Amp可以實(shí)現(xiàn)更快的執(zhí)行速度。
AMPHP是一個(gè)事件驅(qū)動(dòng)的PHP庫(kù)集合,設(shè)計(jì)時(shí)考慮了纖程和并發(fā)性。amphp/amp專(zhuān)門(mén)提供了future和cancellation作為異步編程的基本原語(yǔ)。我們現(xiàn)在使用Revolt,而不是使用amphp/amp發(fā)布事件循環(huán)實(shí)現(xiàn)。
PHP大量使用PHP 8.1附帶的纖程來(lái)編寫(xiě)異步代碼,就像同步、阻塞代碼一樣。與早期版本相比,不需要基于生成器的協(xié)程或回調(diào)。與線(xiàn)程類(lèi)似,每個(gè)纖程都有自己的調(diào)用堆棧,但纖程由事件循環(huán)協(xié)同調(diào)度。使用Amp\async()并發(fā)運(yùn)行。
動(dòng)機(jī)
傳統(tǒng)上,PHP遵循順序執(zhí)行模型。PHP引擎按順序一行接一行地執(zhí)行。然而,程序通常由多個(gè)獨(dú)立的子程序組成,這些子程序可以同時(shí)執(zhí)行。
如果查詢(xún)數(shù)據(jù)庫(kù),則以阻塞方式發(fā)送查詢(xún)并等待數(shù)據(jù)庫(kù)服務(wù)器的響應(yīng)。一旦你有了答案,你就可以開(kāi)始做下一件事。我們可以發(fā)送下一個(gè)數(shù)據(jù)庫(kù)查詢(xún),或者對(duì)一個(gè)API執(zhí)行HTTP調(diào)用,而不是坐在那里什么也不做。讓我們利用我們通?;ㄔ诘却齀/O上的時(shí)間!
Revolt允許這樣的并發(fā)I/O操作。我們通過(guò)避免回調(diào)來(lái)保持低認(rèn)知負(fù)荷。我們的API可以像任何其他庫(kù)一樣使用,除了它們也可以并發(fā)工作,因?yàn)槲覀冊(cè)诤笈_(tái)使用了非阻塞I/O。使用Amp\async()并發(fā)運(yùn)行,并在需要時(shí)使用Future::await()等。
多年來(lái),在PHP中實(shí)現(xiàn)并發(fā)的技術(shù)有很多,例如PHP 5中的回調(diào)和生成器。這些方法都有“你的函數(shù)是什么顏色”的問(wèn)題,我們通過(guò)PHP 8.1中的Fibers解決了這個(gè)問(wèn)題。它們?cè)试S多個(gè)獨(dú)立調(diào)用堆棧的并發(fā)性。
纖程由事件循環(huán)協(xié)同調(diào)度,這就是為什么它們也被稱(chēng)為協(xié)程。重要的是要理解,在任何給定的時(shí)間只有一個(gè)協(xié)程在運(yùn)行,所有其他協(xié)程在此期間暫停。
你可以將協(xié)程比作一臺(tái)使用單個(gè)CPU內(nèi)核運(yùn)行多個(gè)程序的計(jì)算機(jī)。每個(gè)程序都有一個(gè)執(zhí)行時(shí)間段。然而,協(xié)程并不是搶占式的。他們沒(méi)有固定的時(shí)間。他們必須主動(dòng)給予事件循環(huán)的控制權(quán)。
任何阻塞I/O函數(shù)在等待I/O時(shí)阻塞整個(gè)進(jìn)程。你會(huì)想要避開(kāi)他們。如果你還沒(méi)有閱讀安裝指南,可以看看Hello World示例,它演示了阻塞函數(shù)的效果。AMPHP提供的庫(kù)避免了I/O阻塞。
安裝
此包可以作為Composer依賴(lài)項(xiàng)安裝。
composer require amphp/amp
如果您使用這個(gè)庫(kù),很可能希望使用Revolt來(lái)調(diào)度事件,您應(yīng)該單獨(dú)要求Revolt,即使它是作為依賴(lài)項(xiàng)自動(dòng)安裝的。
composer require revolt/event-loop
這些包為PHP中的異步/并發(fā)應(yīng)用程序提供了基本的構(gòu)建塊。我們提供了很多建立在這些基礎(chǔ)上的軟件包。
例如以下
- amphp/byte-stream提供流抽象
- amphp/socket為UDP和TCP(包括TLS)提供套
- amphp/parallel提供并行處理以利用多個(gè)CPU內(nèi)核并卸載阻塞操作
- amphp/http-client提供HTTP/1.1和HTTP/2客戶(hù)端
- amphp/http-server提供HTTP/1.1和HTTP/2應(yīng)用服務(wù)器
- amphp/mysql和amphp/postgres用于非阻塞數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)
要求
此軟件包需要PHP 8.1或更高版本。無(wú)需擴(kuò)展!僅當(dāng)應(yīng)用需要大量并發(fā)套接字連接時(shí)才需要擴(kuò)展,通常此限制配置為最多1024個(gè)文件描述符。
使用
協(xié)程
協(xié)同程序是可中斷的功能。在PHP中,它們可以使用纖程來(lái)實(shí)現(xiàn)。
以前版本的JavaScript使用生成器來(lái)實(shí)現(xiàn)類(lèi)似的目的,但是纖程可以在調(diào)用堆棧中的任何地方中斷,這使得以前的樣板文件(如Amp\call())變得不必要。
在任何給定的時(shí)間,只有一個(gè)纖程在運(yùn)行。當(dāng)協(xié)程掛起時(shí),協(xié)程的執(zhí)行會(huì)暫時(shí)中斷,允許其他任務(wù)運(yùn)行。一旦計(jì)時(shí)器到期,流操作可能,或任何等待的Future完成,執(zhí)行將恢復(fù)。
協(xié)同程序的低級(jí)掛起和恢復(fù)由Revolt的SuspensionAPI處理。
<?php
require __DIR__ . '/vendor/autoload.php';
use Revolt\EventLoop;
$suspension = EventLoop::getSuspension();
EventLoop::delay(5, function () use ($suspension): void {
print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;
$suspension->resume(null);
});
print '++ Suspending to event loop...' . PHP_EOL;
$suspension->suspend();
print '++ Script end' . PHP_EOL;
在Revolt事件循環(huán)上注冊(cè)的回調(diào)會(huì)自動(dòng)作為協(xié)程運(yùn)行,掛起它們是安全的。除了事件循環(huán)API,Amp\async()還可以用來(lái)啟動(dòng)獨(dú)立的調(diào)用棧。
<?php
use function Amp\delay;
require __DIR__ . '/vendor/autoload.php';
Amp\async(function () {
print '++ Executing callback passed to async()' . PHP_EOL;
delay(3);
print '++ Finished callback passed to async()' . PHP_EOL;
});
print '++ Suspending to event loop...' . PHP_EOL;
delay(5);
print '++ Script end' . PHP_EOL;