Skip to main content

什麼是並行處理

當多個彼此不相依的處理(例如向多個外部 API 發出請求、資料庫的匯總處理等)依序執行時,總耗時等於各處理時間的加總。 若能同時執行,總耗時就能縮短為最耗時的那一項處理。 Laravel 的 Concurrency Facade 以簡單的 API 實作這樣的並行執行。
Concurrency Facade 於 Laravel 11 中引入,Laravel 13 仍可繼續使用。 預設驅動使用子 PHP 程序,因此不需額外安裝套件即可運作。

運作機制

Concurrency Facade 會將傳入的閉包序列化後,送至一個隱藏的 Artisan 指令,並以各自獨立的 PHP 程序執行它們。 處理完成後會將回傳值序列化後傳回父程序。 共提供三種驅動:

基本用法

Concurrency::run()

將閉包陣列傳入 run() 方法後,即會並行執行。 回傳值會以陣列形式接收各閉包所回傳的結果。
可透過陣列解構將各結果分別賦值到變數中。 閉包的順序與回傳值的順序一致。

指定驅動

若想使用特定驅動,可透過 driver() 方法指定。
要更改預設驅動,可將設定檔發布,並更新 default 選項。

fork 驅動的用法

fork 驅動比 process 驅動更快,但只能在 PHP 的 CLI 環境(Artisan 指令或佇列 worker)中使用。 無法在 Web 請求中使用。 使用前請先安裝 spatie/fork 套件。
fork 驅動無法在 Web 請求中運作。當你想在 Artisan 指令或佇列 worker 中做並行處理時再使用。

不接收執行結果:Concurrency::defer()

若你並不關心處理的結果,只希望在傳回 HTTP 回應之後,於背景執行,可以使用 defer() 方法。
呼叫 defer() 當下,閉包並不會立即執行。 它會在 HTTP 回應送出給使用者之後,並行地執行。
對於不必讓使用者等待的處理,例如記錄分析資料或預熱快取,defer() 是最佳選擇。

實戰範例:同時呼叫多個外部 API

想像一個電商網站的儀表板,需要從庫存管理 API、業績匯總 API、配送狀態 API 三處取得資訊。

依序執行(改善前)

並行執行(改善後)

若每個 API 花費 1 秒,依序執行需 3 秒,並行執行則約 1 秒即可完成。

同時執行多個資料庫匯總

使用 process 驅動進行資料庫匯總時,每個子程序都會建立新的資料庫連線。 並行數量較多時,請注意資料庫的最大連線數。

Context 自動傳遞

Laravel 13.31(#61419)起,process 驅動會將目前的 Context 自動傳遞至子程序。 在此之前,透過 Context::add() 設定的值並不會被繼承給以 Concurrency::run()Concurrency::defer() 啟動的子程序,日誌中輸出的 context 可能在父程序與子程序間不一致。 修正後,內部使用的機制與傳遞 context 給佇列 Job 相同。
  1. 在啟動子程序前,將 Context::dehydrate() 的結果序列化為 JSON,透過 __LARAVEL_CONTEXT 環境變數傳遞
  2. 子程序(隱藏的 Artisan 指令 invoke-serialized-closure)啟動後,會讀取 __LARAVEL_CONTEXT 並對 Context repository 執行 hydrate()
因此,在 closure 外部設定的 context,在 closure 內部可以直接參照。
fork 驅動使用 spatie/fork 進行 OS 層級的 fork(),子程序會直接複製父程序的記憶體空間啟動。 因此 context 本來就會自然共享,本次修正主要針對 process 驅動(啟動另一個程序的方式)。
若將 request ID、使用者 ID 等用於日誌關聯的值放入 Context,以 Concurrency::run() 並行執行的處理,其日誌也能以相同的 key 進行追蹤。 詳細用法請參閱共享 context

測試時的設定

測試環境中可使用 sync 驅動,讓閉包依序執行。 沒有並行執行所需的程序啟動成本,能加速測試。
也可以在 .env.testing 更改預設驅動。

注意事項

閉包會被序列化後傳給子程序。 無法從閉包外部捕獲不可序列化的物件(資料庫連線、檔案 handle、resource 等)。 需要的物件請在閉包內另行建立。
process 驅動有子程序啟動成本,因此當處理本身非常短(低於幾毫秒)時,並行執行未必更快。 對於 HTTP 請求或資料庫匯總等耗時超過 100ms 的處理,並行化才會顯現效果。
fork 驅動會 fork PHP 程序,因此無法在 Web 請求(FPM 或 Apache)中使用。 僅能在 Artisan 指令或佇列 worker 中使用。
若在並行執行過程中發生例外,run() 會將例外重新拋出。 若希望即使個別處理發生例外,其他處理仍能繼續執行,請在閉包內使用 try/catch

下一步

佇列與工作

學習如何將處理透過佇列與工作,非同步地在背景執行
最後修改於 2026年9月11日