Zum Hauptinhalt springen

Was ist nebenläufige Verarbeitung?

Wenn Sie mehrere Anfragen an externe APIs oder Datenbank-Aggregationen, die nicht voneinander abhängen, nacheinander ausführen, entspricht die Gesamtdauer der Summe der einzelnen Laufzeiten. Führen Sie sie hingegen gleichzeitig aus, verkürzt sich die Gesamtdauer auf die Zeit der langsamsten Einzelverarbeitung. Die Facade Concurrency von Laravel realisiert eine solche nebenläufige Ausführung über eine einfache API.
Die Facade Concurrency wurde in Laravel 11 eingeführt und steht auch in Laravel 13 weiterhin zur Verfügung. Der Standardtreiber verwendet PHP-Kindprozesse und benötigt keine zusätzlichen Pakete.

Funktionsweise

Die Facade Concurrency serialisiert die übergebenen Closures, schickt sie an einen versteckten Artisan-Befehl und lässt sie jeweils in einem separaten PHP-Prozess ausführen. Nach Abschluss werden die Rückgabewerte serialisiert und an den Elternprozess zurückgereicht. Es stehen drei Treiber zur Verfügung:
TreiberBeschreibung
processStandard. Startet PHP-Kindprozesse für die Ausführung. Funktioniert auch im Kontext eines Web-Requests.
forkBenötigt das Paket spatie/fork. Erzeugt Kindprozesse per Fork; nur im CLI verwendbar, aber sehr schnell.
syncFührt die Closures nicht nebenläufig, sondern nacheinander aus. Eignet sich für Tests.

Grundlegende Verwendung

Concurrency::run()

Übergeben Sie der Methode run() ein Array mit Closures, werden diese nebenläufig ausgeführt. Der Rückgabewert enthält die Ergebnisse der jeweiligen Closures als Array.
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
]);
Sie können die Ergebnisse mit einem Array-Destructuring in einzelne Variablen aufteilen. Die Reihenfolge der Closures entspricht der Reihenfolge der Rückgabewerte.

Treiber angeben

Um einen bestimmten Treiber zu verwenden, nutzen Sie die Methode driver().
$results = Concurrency::driver('fork')->run([
    fn () => fetchFromApiA(),
    fn () => fetchFromApiB(),
]);
Um den Standardtreiber zu ändern, veröffentlichen Sie die Konfigurationsdatei und passen die Option default an.
php artisan config:publish concurrency

Verwendung des fork-Treibers

Der fork-Treiber ist schneller als der process-Treiber, funktioniert aber ausschließlich in PHP-CLI-Umgebungen (z. B. in Artisan-Befehlen oder Queue-Workern). Innerhalb eines Web-Requests kann er nicht verwendet werden. Installieren Sie vor der Nutzung das Paket spatie/fork.
composer require spatie/fork
Der fork-Treiber funktioniert nicht während eines Web-Requests. Verwenden Sie ihn, wenn Sie nebenläufige Verarbeitung innerhalb eines Artisan-Befehls oder Queue-Workers wünschen.

Ohne Rückgabewerte: Concurrency::defer()

Interessieren Sie sich nicht für die Ergebnisse und möchten die Verarbeitung im Hintergrund nach dem Senden der HTTP-Antwort ausführen, verwenden Sie die Methode defer().
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
Beim Aufruf von defer() werden die Closures noch nicht ausgeführt. Sie laufen erst nebenläufig, nachdem die HTTP-Antwort an den Nutzer gesendet wurde.
defer() eignet sich hervorragend für Arbeiten, bei denen der Nutzer nicht warten muss – etwa das Aufzeichnen von Analytics oder das Vorwärmen von Caches.

Praxisbeispiel: mehrere externe APIs gleichzeitig aufrufen

Nehmen wir das Dashboard eines E-Commerce-Systems, das Daten aus drei APIs bezieht: Bestandsführung, Umsatzstatistik und Versandstatus.

Sequenzielle Ausführung (vor der Optimierung)

use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Auf jede Anfrage nacheinander warten (insgesamt ca. 3 Sekunden)
    $inventory = Http::get('https://api.example.com/inventory')->json();
    $sales     = Http::get('https://api.example.com/sales')->json();
    $shipping  = Http::get('https://api.example.com/shipping')->json();

    return compact('inventory', 'sales', 'shipping');
}

Nebenläufige Ausführung (nach der Optimierung)

use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Alle drei Anfragen gleichzeitig ausführen (Dauer = langsamste API)
    [$inventory, $sales, $shipping] = Concurrency::run([
        fn () => Http::get('https://api.example.com/inventory')->json(),
        fn () => Http::get('https://api.example.com/sales')->json(),
        fn () => Http::get('https://api.example.com/shipping')->json(),
    ]);

    return compact('inventory', 'sales', 'shipping');
}
Braucht jede API 1 Sekunde, ergibt sich sequenziell eine Gesamtlaufzeit von 3 Sekunden, nebenläufig hingegen nur etwa 1 Sekunde.

Mehrere Datenbank-Aggregationen gleichzeitig

use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

public function statistics(): array
{
    [$totalUsers, $activeUsers, $totalOrders, $revenue] = Concurrency::run([
        fn () => DB::table('users')->count(),
        fn () => DB::table('users')->where('active', true)->count(),
        fn () => DB::table('orders')->count(),
        fn () => DB::table('orders')->sum('total_amount'),
    ]);

    return [
        'total_users'  => $totalUsers,
        'active_users' => $activeUsers,
        'total_orders' => $totalOrders,
        'revenue'      => $revenue,
    ];
}
Wenn Sie den process-Treiber für Datenbank-Aggregationen verwenden, öffnet jeder Kindprozess eine eigene Datenbankverbindung. Achten Sie bei hoher Parallelität auf die maximale Verbindungsanzahl Ihrer Datenbank.

Konfiguration für Tests

In Testumgebungen führt der sync-Treiber die Closures nacheinander aus. Es entfällt der Overhead durch das Starten von Prozessen, wodurch Tests schneller laufen.
// tests/Feature/DashboardTest.php
use Illuminate\Support\Facades\Concurrency;

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake(); // Auf den sync-Treiber umschalten

    $response = $this->get('/dashboard');

    $response->assertStatus(200);
}
Alternativ können Sie den Standardtreiber in .env.testing festlegen.
CONCURRENCY_DRIVER=sync

Hinweise

Closures werden serialisiert und an Kindprozesse übergeben. Objekte, die nicht serialisierbar sind (Datenbankverbindungen, Datei-Handles, Ressourcen usw.), können nicht von außerhalb der Closure eingefangen werden. Erstellen Sie die benötigten Objekte innerhalb der Closure neu.
// Schlecht: ein Eloquent-Modell von außerhalb einfangen
$user = User::find(1);
Concurrency::run([
    fn () => $user->orders()->count(), // Möglicherweise nicht serialisierbar
]);

// Gut: Daten innerhalb der Closure laden
$userId = 1;
Concurrency::run([
    fn () => Order::where('user_id', $userId)->count(),
]);
Der process-Treiber ist mit dem Aufwand für das Starten eines Kindprozesses verbunden. Ist die Verarbeitung selbst sehr kurz (wenige Millisekunden), bringt die nebenläufige Ausführung möglicherweise keinen Vorteil. Ihre Stärke spielt sie bei Arbeiten von 100 ms und mehr aus – etwa HTTP-Anfragen oder Datenbank-Aggregationen.
Der fork-Treiber forkt einen PHP-Prozess und kann daher nicht innerhalb eines Web-Requests (FPM oder Apache) genutzt werden. Verwenden Sie ihn ausschließlich in Artisan-Befehlen oder Queue-Workern.
Tritt während der nebenläufigen Ausführung eine Ausnahme auf, wirft run() diese erneut. Wenn Sie möchten, dass andere Verarbeitungen weiterlaufen, obwohl eine Closure fehlschlägt, fangen Sie die Ausnahme innerhalb der Closure mit try/catch ab.
Concurrency::run([
    function () {
        try {
            return Http::get('https://api.example.com/data')->json();
        } catch (\Exception $e) {
            return null; // Bei Fehler null zurückgeben
        }
    },
]);

Nächste Schritte

Queues und Jobs

Erfahren Sie, wie Sie Arbeit asynchron im Hintergrund über Queues und Jobs verarbeiten.
Zuletzt geändert am 13. Juli 2026