Sepanjang section ini kita menangani pekerjaan latar belakang dengan senjata klasik: queue dan proses terpisah. Ada pendekatan lain yang selama ini kita lewatkan: menjadikan satu proses PHP sendiri sanggup menangani ribuan koneksi sekaligus. Dunia ini disebut async PHP — event loop, callback, Fiber, dan coroutine — dengan nama-nama seperti ReactPHP, AMPHP, dan Swoole. Chapter penutup section ini tidak akan menjadikan Anda ahli runtime async dalam satu bacaan; tujuannya lebih penting dari itu: memahami cara kerjanya di bawah kap, kapan ia benar-benar membantu, kapan tidak, dan bagaimana ekosistemnya jujur kita nilai. Dengan peta yang benar, keputusan “sync + queue atau async” tidak lagi diambil berdasarkan FOMO.

Model Eksekusi Sinkron PHP #

Kondisi awal yang harus dipahami adalah model bawaan PHP: sinkron dan blocking. Ketika kode memanggil file_get_contents() pada URL atau PDO::query(), interpreter berhenti di baris itu — tidak menulis respons, tidak menghitung apa pun — sampai operasi I/O selesai. Di web tradisional hal ini tidak masalah karena PHP-FPM menjalankan puluhan proses worker paralel, sehingga blocking satu worker tidak menghentikan yang lain. Persoalan muncul ketika satu proses harus menangani banyak pekerjaan I/O sekaligus: menunggu secara berurutan membuat total waktu menumpuk.

Perhitungan sederhana berikut memperlihatkan mengapa urutan menjadi mahal. Asumsikan satu request checkout memanggil tiga API eksternal:

php
<?php

$panggilanApi = [
    ["layanan" => "cek stok gudang", "latensi_ms" => 120],
    ["layanan" => "hitung ongkir", "latensi_ms" => 150],
    ["layanan" => "validasi alamat", "latensi_ms" => 80],
];

$latensi = array_column($panggilanApi, "latensi_ms");

$totalBerurutan = array_sum($latensi);
$latensiTerpanjang = max($latensi);

echo "Tiga panggilan API berurutan (blocking): {$totalBerurutan} ms\n";
echo "Tiga panggilan API tumpang tindih       : {$latensiTerpanjang} ms\n";

Output yang Dihasilkan:

text
Tiga panggilan API berurutan (blocking): 350 ms
Tiga panggilan API tumpang tindih       : 150 ms

Dijalankan berurutan, total menunggu adalah penjumlahan seluruh latensi: 350 ms. Dijalankan tumpang tindih (concurrent), total menunggu hanya sepanjang latensi terpanjang: 150 ms — dua panggilan lain menunggu di saat yang sama. Perhatikan apa yang tidak berubah: ketiga panggilan tetap memakan 350 ms waktu kerja jaringan; yang berubah hanyalah siapa yang menunggu. Inilah janji dasar async: menghemat waktu tunggu, bukan mempercepat pekerjaan itu sendiri. Untuk mencapainya, satu proses harus bisa memulai I/O, meninggalkannya, mengerjakan yang lain, dan kembali saat hasilnya siap — tugas yang dikerjakan event loop.

Event Loop sebagai Jantung Runtime Async #

Event loop adalah satu loop tanpa henti yang mengelola daftar tugas dan sumber event. Siklus hidupnya sederhana: eksekusi kode tugas yang siap, catat I/O yang belum selesai, lalu tanyakan ke sistem operasi (“ada yang selesai?”) lewat mekanisme seperti select()/epoll(), dan ulangi. Selama menunggu I/O, thread tidak terblokir — inilah satu-satunya cara satu thread menangani ribuan koneksi.

Diagram tersebut memperlihatkan dua aturan main yang membuat loop tetap sehat: setiap tugas berjalan hanya sampai titik jeda (yield, suspend, atau callback I/O), dan pemeriksaan event I/O dilakukan lewat sistem operasi, bukan dengan sibuk bertanya berulang. Konsekuensi aturan pertama adalah sifat kooperatif: jika sebuah tugas menulis sleep(30) atau query blocking biasa di tengah kode, seluruh loop ikut berhenti 30 detik — ribuan koneksi lain ikut membeku. Runtime async menyediakan versi non-blocking untuk hampir semua operasi I/O (HTTP client, timer, stream), dan disiplin memakainya adalah tanggung jawab programmer, bukan runtime.

Fiber sebagai Blok Bangunan #

Sejak PHP 8.1, bahasa itu sendiri menyediakan primitif untuk menunda dan melanjutkan eksekusi: Fiber . Fiber adalah segmen eksekusi berstack penuh yang bisa menghentikan dirinya (Fiber::suspend()) dan dilanjutkan (resume()) dari luar. Ia tidak menjadwalkan apa pun — hanya menyerahkan kendali. Justru kesederhanaan itulah yang membuatnya pas menjadi blok bangunan: event loop boleh tetap sederhana, sementara kode aplikasi ditulis seolah-olah sinkron biasa, dan jeda-jeda kooperatifnya ditangangi Fiber di belakang layar:

php
<?php

$fiberPekerja = new Fiber(function (string $namaTugas): string {
    $hasilSementara = "data mentah dari {$namaTugas}";

    // titik jeda kooperatif: kembalikan kendali ke pemanggil
    $masukanLanjutan = Fiber::suspend($hasilSementara);

    return "hasil akhir {$namaTugas} ({$masukanLanjutan})";
});

$nilaiSuspend = $fiberPekerja->start("import katalog");
echo "Pemanggil menerima : {$nilaiSuspend}\n";

$fiberPekerja->resume("data divalidasi");
echo "Pemanggil menerima : " . $fiberPekerja->getReturn() . "\n";

Output yang Dihasilkan:

text
Pemanggil menerima : data mentah dari import katalog
Pemanggil menerima : hasil akhir import katalog (data divalidasi)

Mari kita ikuti alurnya: start() menjalankan closure sampai bertemu Fiber::suspend(), yang membekukan eksekusi di tengah fungsi dan mengembalikan nilai ke pemanggil — baris pertama output tercetak saat fungsi itu “terkunci” di tengah jalan. resume() kemudian mencairkan eksekusi tepat di posisi sembari menyuntikkan nilai balik ke variabel $masukanLanjutan, dan fungsi berjalan sampai return yang bisa dibaca lewat getReturn(). Dalam runtime async sesungguhnya, suspend-resume ini dibungkus library sehingga kode Anda cukup menulis $hasil = $client->get($url); — ketika respons belum siap, Fiber menyerahkan loop untuk mengerjakan tugas lain, dan eksekusi melanjut seolah tidak terjadi apa-apa.

Multitasking Kooperatif dengan yield #

Cara paling telanjang memahami kooperatif adalah menyusunnya sendiri dengan Generator — fitur yang sudah lama kita kenal. Setiap yield adalah titik jeda; satu loop sederhana memutar dua tugas bergantian:

php
<?php

function tugasMultiLangkah(string $namaTugas): Generator
{
    foreach (["siapkan koneksi", "tunggu respons", "tulis hasil"] as $langkah) {
        yield "{$namaTugas}: {$langkah}";
    }
}

$tugasEmail = tugasMultiLangkah("kirim email");
$tugasPdf = tugasMultiLangkah("buat PDF");

while ($tugasEmail->valid() || $tugasPdf->valid()) {
    foreach ([$tugasEmail, $tugasPdf] as $tugas) {
        if ($tugas->valid()) {
            echo $tugas->current() . "\n";
            $tugas->next();
        }
    }
}

Output yang Dihasilkan:

text
kirim email: siapkan koneksi
buat PDF: siapkan koneksi
kirim email: tunggu respons
buat PDF: tunggu respons
kirim email: tulis hasil
buat PDF: tulis hasil

Output memperlihatkan interleave: langkah “kirim email” dan “buat PDF” berselang-seling, bukan berurutan — padahal hanya ada satu thread yang mengeksekusi keduanya. Loop kecil di bawah berperan sebagai scheduler, yield berperan sebagai titik menyerahkan kendali, dan tidak ada satu pun langkah yang menunggu langkah lain selesai. Scheduler event loop sungguhan melakukan hal serupa dengan jutaan putaran, memilih tugas yang I/O-nya sudah selesai. Fiber dan Generator sesungguhnya adalah saudara: keduanya menyediakan jeda kooperatif; Fiber menambahkan stack penuh sehingga suspend bisa terjadi di kedalaman pemanggilan fungsi, bukan hanya di fungsi generator itu sendiri.

Peta Runtime Async PHP #

Ekosistem async PHP terbagi ke beberapa proyek dengan pendekatan berbeda. Mengetahui peta ini membantu Anda membaca lowongan kerja dan dokumentasi framework tanpa bingung:

RuntimePendekatanKarakter
ReactPHPEvent loop + callbackPelopor modern; komponen event loop-nya jadi fondasi banyak proyek
AMPHP v3Event loop + Fiber (di atas Revolt)Async/await bergaya sinkron via Fiber; ekosistem socket, HTTP, dan parallel
RevoltEvent loop generik berbasis FiberLapisan event loop minimalis yang dipakai AMPHP v3
SwooleEkstensi C, coroutine + serverPerforma tinggi; coroutine otomatis pada I/O bawaan; butuh ekstensi terpasang
OpenSwooleFork komunitas dari SwooleAPI serupa Swoole dengan pengembangan terpisah

Ada dua keluarga besar di tabel tersebut. Keluarga userland (ReactPHP, AMPHP, Revolt) berjalan pada PHP biasa tanpa ekstensi tambahan: library-lah yang menyediakan versi non-blocking dari operasi I/O, dan Anda bebas memakainya per-komponen. Keluarga ekstensi C (Swoole, OpenSwoole) menempel pada interpreter itu sendiri: server HTTP, coroutine, dan penjadwalan hidup di dalam ekstensi, sehingga operasi I/O bawaan otomatis non-blocking — dengan harga berupa proses deploy dan perilaku runtime yang berbeda dari PHP-FPM.

Kejujuran tentang kematangan ekosistem juga perlu disampaikan: mayoritas aplikasi PHP produksi di dunia tetap berjalan sinkron di atas PHP-FPM, diperkuat queue dan worker — persis arsitektur yang kita pelajari sepanjang section ini. Runtime async menempati ceruk yang jelas: server websocket, chat, proxy, dan layanan dengan puluhan ribu koneksi hidup yang tidak masuk akal ditangani puluhan ribu proses. Dokumentasi, paket Composer, dan jawaban Stack Overflow untuk dunia async juga jauh lebih tipis daripada dunia sinkron; memetik untung dari async menuntut kesediaan membaca sumber library. Ini bukan alasan untuk menghindar, melainkan alasan untuk memilih dengan sadar.

Kapan Async Membantu dan Kapan Tidak #

Keputusan async hidup atau mati pada satu pertanyaan: kemana waktu aplikasi Anda habis? Kalau mayoritas waktu menunggu I/O (jaringan, disk, database jauh), async memberi keuntungan besar karena menunggu bisa ditumpuk. Kalau mayoritas waktu berubah CPU (render PDF, kompresi, kalkulasi), async nyaris tidak menolong — satu thread kooperatif tetap hanya menghitung satu hal pada satu waktu; parallelisme sejati untuk CPU tetap butuh proses atau thread terpisah seperti pada chapter Process Management .

Profil bebanContohAlat yang tepat
I/O-berat, banyak koneksi hidupGateway websocket, chat, proxyRuntime async (event loop + Fiber)
I/O-berat, tugas latar belakangKirim email, sinkronisasi dataQueue + worker (materi section ini)
CPU-beratRender video, kompresi massalProses paralel, pcntl_fork()
KombinasiAPI aggregator, scraperAsync untuk I/O + proses untuk CPU

Baris kedua tabel tersebut penting dicermati: untuk mayoritas pekerjaan latar belakang, queue dan worker sudah memadai dan jauh lebih sederhana dioperasikan. Async unggul ketika concurrency harus hidup di dalam satu proses — misalnya server harus menjaga 10.000 koneksi websocket sambil sesekali memanggil API.

Jebakan State yang Dibagi #

Multitasking kooperatif terasa aman karena “tidak ada dua kode berjalan di saat yang sama” — dan memang benar, sampai titik jeda. Jebakannya justru lahir dari titik jeda itu: dua tugas bergantian membaca-menulis variabel yang sama, dan urutan interleave yang berubah mengubah hasil akhirnya. Counter yang tidak di-increment secara atomik, cache yang ditulis dua tugas bersamaan, atau object sementara yang dipakai lintas jeda adalah sumber bug yang sulit direproduksi karena hanya muncul pada urutan interleaving tertentu.

Pertahanan yang terbukti sehat: perlakukan state dengan disiplin yang sama seperti berbagi memori antar proses. Ringankan shared state — kumpulkan input per-tugas dan proses di satu tempat; bungkus counter dengan struktur yang menjamin operasi selesai sebelum titik jeda; hindari menahan lock melewati await/suspend; dan utamakan message passing (queue kecil, channel) di atas tulis-bersama. Menguji urutan interleave yang berbeda-beda juga membantu — satu nilai tambah besar dari menulis logika sebagai fungsi kecil murni yang bisa diuji, kebiasaan yang akan kita tumpu penuh di Section Testing dan Quality Assurance.

Ringkasan #

Model bawaan PHP sinkron dan blocking; async mengubahnya dengan event loop yang menumpuk waktu tunggu I/O di satu thread kooperatif. Fiber menyediakan jeda ber-stack penuh sebagai blok bangunan bahasa, Generator mengajarkan pola interleave-nya, dan ekosistem terbagi antara keluarga userland (ReactPHP, AMPHP, Revolt) dan keluarga ekstensi C (Swoole, OpenSwoole). Async membantu ketika beban I/O-berat dengan banyak koneksi hidup, sementara tugas latar belakang biasa tetap paling sehat di queue dan worker, dan beban CPU butuh proses paralel. Peta ini menutup perjalanan kita di dunia asynchronous processing — dan membuka pintu kebalikan yang tak kalah penting: membuktikan bahwa semua kode ini bekerja, lewat pengujian, pada Section Testing dan Quality Assurance.

Referensi #