AMPHP 是 PHP 事件驅動函式庫的集合,在設計時考慮了光纖和並發性。 amphp/amp
專門提供了 future 和取消作為非同步程式設計的基本原語。我們現在使用 Revolt,而不是使用amphp/amp
提供事件循環實作。
Amp 大量使用 PHP 8.1 隨附的纖程來編寫非同步程式碼,就像同步、阻塞程式碼一樣。與早期版本相比,不需要基於生成器的協程或回呼。與執行緒類似,每個纖程都有自己的呼叫堆疊,但纖程由事件循環協同調度。使用Ampasync()
並發運行。
傳統上,PHP 遵循順序執行模型。 PHP 引擎依序執行一行又一行。然而,程式通常由多個可以同時執行的獨立子程式組成。
如果查詢資料庫,則發送查詢並以阻塞方式等待資料庫伺服器的回應。一旦得到回應,你就可以開始做下一步了。我們可以發送下一個資料庫查詢,或對 API 進行 HTTP 調用,而不是坐在那裡等待。讓我們充分利用平常等待 I/O 的時間吧!
Revolt 允許此類並發 I/O 操作。我們透過避免回調來保持較低的認知負荷。我們的 API 可以像任何其他庫一樣使用,但它們也可以同時工作,因為我們在底層使用非阻塞 I/O。使用Ampasync()
同時運行,並在需要時使用Future::await()
等待結果!
多年來,在PHP 中實現並發的技術多種多樣,例如PHP 5 中提供的回調和生成器。問題。它們允許多個獨立呼叫堆疊的並發。
纖程由事件循環協作調度,這就是它們也稱為協程的原因。重要的是要理解,在任何給定時間只有一個協程正在運行,所有其他協程同時都被掛起。
您可以將協程與使用單一 CPU 核心執行多個程式的電腦進行比較。每個程式都有一個執行時隙。然而,協程並不是搶佔式的。他們沒有固定的時間段。他們必須自願放棄對事件循環的控制。
任何阻塞 I/O 函數都會在等待 I/O 時阻塞整個進程。你會想要避開它們。如果您還沒有閱讀安裝指南,請查看演示阻止功能效果的 Hello World 範例。 AMPHP 提供的庫避免了 I/O 阻塞。
該軟體包可以作為 Composer 依賴項安裝。
composer require amphp/amp
如果您使用此程式庫,您很可能想要使用 Revolt 來安排事件,您應該單獨需要它,即使它會作為依賴項自動安裝。
composer require revolt/event-loop
這些套件為 PHP 中的非同步/並發應用程式提供了基本構建塊。我們提供了許多基於這些的軟體包,例如
amphp/byte-stream
提供流抽象amphp/socket
為 UDP 和 TCP(包括 TLS)提供套接字層amphp/parallel
提供平行處理以利用多個 CPU 核心並卸載阻塞操作amphp/http-client
提供 HTTP/1.1 和 HTTP/2 用戶端amphp/http-server
提供 HTTP/1.1 和 HTTP/2 應用伺服器amphp/mysql
和amphp/postgres
用於非阻塞資料庫訪問該軟體包需要 PHP 8.1 或更高版本。無需擴充!
只有當您的應用程式需要大量並發套接字連接時才需要擴展,通常此限製配置為最多 1024 個檔案描述符。
協程是可中斷的函數。在 PHP 中,它們可以使用纖程來實現。
注意先前版本的 Amp 使用生成器來實現類似的目的,但是纖程可以在呼叫堆疊中的任何位置中斷,從而不再需要像
Ampcall()
這樣的先前的樣板檔案。
在任何給定時間,只有一根光纖在運作。當協程掛起時,協程的執行會暫時中斷,從而允許其他任務運行。一旦計時器到期、可以進行流程操作或任何等待的Future
完成,就會恢復執行。
協程的低階暫停和復原由 Revolt 的Suspension
API 處理。
<?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 事件循環上註冊的回呼會自動作為協程運行,並且可以安全地掛起它們。除了事件循環 API 之外, Ampasync()
還可用於啟動獨立的呼叫堆疊。
<?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 ;
Future
是一個表示非同步操作的最終結果的物件。有以下三種狀態:
成功完成的 future 類似於傳回值,而錯誤的 future 類似於拋出例外。
處理非同步 API 的一種方法是使用在操作啟動時傳遞並在操作完成後呼叫的回調:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
回調方法有幾個缺點。
這就是期貨發揮作用的地方。它們是像任何其他返回值一樣傳回的結果的佔位符。呼叫者可以選擇使用Future::await()
等待結果或註冊一個或多個回呼。
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
在並發應用程式中,將會有多個 future,您可能想要等待所有這些 future,或只等待第一個 future。
AmpFutureawait($iterable, $cancellation)
等待iterable
的所有Future
物件。如果Future
實例之一出錯,則操作會因該異常而中止。否則,結果是一個將輸入可iterable
中的鍵與其完成值相符的陣列。
await()
組合器非常強大,因為它允許您同時並發執行許多非同步操作。讓我們來看一個使用amphp/http-client
同時檢索多個 HTTP 資源的範例:
<?php
use Amp Future ;
use Amp Http Client HttpClientBuilder ;
use Amp Http Client Request ;
$ httpClient = HttpClientBuilder:: buildDefault ();
$ uris = [
" google " => " https://www.google.com " ,
" news " => " https://news.google.com " ,
" bing " => " https://www.bing.com " ,
" yahoo " => " https://www.yahoo.com " ,
];
try {
$ responses = Future await ( array_map ( function ( $ uri ) use ( $ httpClient ) {
return Amp async ( fn () => $ httpClient -> request ( new Request ( $ uri , ' HEAD ' )));
}, $ uris ));
foreach ( $ responses as $ key => $ response ) {
printf (
" %s | HTTP/%s %d %s n" ,
$ key ,
$ response -> getProtocolVersion (),
$ response -> getStatus (),
$ response -> getReason ()
);
}
} catch ( Exception $ e ) {
// If any one of the requests fails the combo will fail
echo $ e -> getMessage (), "n" ;
}
AmpFutureawaitAnyN($count, $iterable, $cancellation)
與await()
相同,只是它可以容忍個別錯誤。一旦iterable
物件中的$count
個實例成功完成,就會傳回結果。傳回值是一個值數組。組件數組中的各個鍵是從傳遞給函數進行評估的iterable
中保留的。
AmpFutureawaitAll($iterable, $cancellation)
等待所有 future 並將其結果作為[$errors, $values]
數組傳回。
AmpFutureawaitFirst($iterable, $cancellation)
解包第一個完成的Future
,無論是成功完成或出錯。
AmpFutureawaitAny($iterable, $cancellation)
解開第一個成功完成的Future
。
期貨可以透過多種方式創建。大多數程式碼將使用Ampasync()
它接受一個函數並將其作為另一個 Fiber 中的協程運行。
有時,介面要求會傳回Future
,但結果可以立即可用,例如因為它們已被快取。在這些情況下, Future::complete(mixed)
和Future::error(Throwable)
可用來建構立即完成的Future
。
注意下面描述的
DeferredFuture
API 是許多應用程式可能不需要的高階 API。盡可能使用Ampasync()
或組合器。
AmpDeferredFuture
負責完成待處理的Future
。您建立一個AmpDeferredFuture
並使用其getFuture
方法將AmpFuture
傳回給呼叫者。一旦結果準備好,您可以使用連結的DeferredFuture
上的complete
或error
來完成呼叫者所持有的Future
。
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
警告如果您傳遞
DeferredFuture
對象,那麼您可能做錯了什麼。它們應該是您操作的內部狀態。
警告你不能用另一個未來來完成一個未來;在這種情況下,在呼叫
DeferredFuture::complete()
之前使用Future::await()
。
以下是一個簡單的範例,非同步值產生器asyncMultiply()
建立DeferredFuture
並將關聯的Future
傳回給其呼叫者。
<?php // Example async producer using DeferredFuture
use Revolt EventLoop ;
function asyncMultiply ( int $ x , int $ y ): Future
{
$ deferred = new Amp DeferredFuture ;
// Complete the async result one second from now
EventLoop:: delay ( 1 , function () use ( $ deferred , $ x , $ y ) {
$ deferred -> complete ( $ x * $ y );
});
return $ deferred -> getFuture ();
}
$ future = asyncMultiply ( 6 , 7 );
$ result = $ future -> await ();
var_dump ( $ result ); // int(42)
每個支援取消的操作都接受Cancellation
實例作為參數。取消是允許註冊處理程序訂閱取消請求的物件。這些物件被傳遞給子操作或必須由操作本身處理。
一旦請求取消, $cancellation->throwIfRequested()
可用於使目前操作失敗並拋出CancelledException
。雖然throwIfRequested()
效果很好,但某些操作可能需要使用回調來訂閱。他們可以使用Cancellation::subscribe()
來訂閱可能發生的任何取消請求。
呼叫者使用以下實作之一建立Cancellation
。
注意取消僅供參考。 DNS 解析器可能會在發送查詢後忽略取消請求,因為無論如何都必須處理回應並且仍然可以快取回應。 HTTP 用戶端可能會繼續即將完成的 HTTP 請求以重複使用連接,但可能會中止分塊編碼回應,因為它無法知道繼續是否實際上比中止更便宜。
TimeoutCancellations
在指定的秒數後會自動取消。
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
當前進程收到指定訊號後, SignalCancellation
會自動取消。
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
DeferredCancellation
允許透過呼叫方法來手動取消。如果您需要在某處註冊一些自訂回調而不是交付自己的實現,那麼這是首選方法。只有呼叫者有權存取DeferredCancellation
並且可以使用DeferredCancellation::cancel()
取消操作。
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
NullCancellation
永遠不會被取消。取消通常是可選的,通常透過使參數可為空來實現。為了避免像if ($cancellation)
這樣的守衛,可以使用NullCancellation
來代替。
$ cancellation ??= new NullCancellationToken ();
CompositeCancellation
組合了多個獨立的取消物件。如果這些取消中的任何一個被取消, CompositeCancellation
本身也將被取消。
amphp/amp
與所有其他amphp
軟體包一樣遵循 semver 語意版本控制規範。
相容的套件應使用 GitHub 上的amphp
主題。
如果您發現任何與安全相關的問題,請發送電子郵件至[email protected]
而不是使用問題追蹤器。
麻省理工學院許可證 (MIT)。請參閱LICENSE
以了解更多資訊。