Belajar Swoole - Coroutines: Fundamentals
Episode 7 of 26

Belajar Swoole - Coroutines: Fundamentals

Menggali fondasi coroutine di Swoole: Coroutine::create, go, defer, dan cara kerja scheduler, mengapa coroutine menghilangkan callback hell, hook pada operasi I/O, serta praktik menjalankan banyak request HTTP secara concurrent dalam satu worker.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 6 kita membangun WebSocket dan merasakan kekuatan server realtime — pada episode ini kita menggali mesin yang membuat semua itu mungkin: coroutine. Ini adalah konsep paling khas Swoole dan titik di mana kalian benar-benar berpindah mental dari "developer PHP-FPM" menjadi "developer PHP modern".

Mengapa episode ini penting? Karena coroutine adalah jawaban Swoole atas dilema lama pemrograman async: kode sinkron yang mudah dibaca, dengan performa non-blocking. Setelah episode ini, kalian akan melihat callback-chaining sebagai senjata makan tuan, dan mulai menulis kode concurrency yang bersih.

Analogi: Kasir dan Antrian

Bayangkan sebuah kantin dengan satu kasir (worker) dan banyak pembeli (task). Model PHP-FPM seperti memanggil satu kasir per pembeli: mahal, dan setiap kasir diam saja menunggu pembeli selesai makan sebelum melayani orang berikutnya.

Model coroutine seperti satu kasir yang lihai: saat pembeli mengambil lauk (menunggu I/O), kasir pindah melayani pembeli lain, lalu kembali menagih pembeli pertama saat makanannya siap. Satu kasir melayani banyak pembeli bersamaan — tanpa antrean panjang dan tanpa biaya ekstra per pembeli.

Coroutine::create dan go()

Cara membuat coroutine:

Membuat coroutine
use Swoole\Coroutine;
 
Coroutine::create(function () {
    echo "Dalam coroutine\n";
});
 
echo "Diluar coroutine\n";

Dengan shortname aktif, ada fungsi global go(); tetapi karena kita menonaktifkannya (episode 3), kita selalu memakai nama lengkap. Bila dipanggil di dalam context coroutine, bisa juga memakai:

Coroutine::create dari dalam coroutine
Coroutine::create(function () {
    Coroutine::create(function () {
        echo "Coroutine bersarang\n";
    });
});

Coroutine bersarang diperbolehkan, tetapi setiap coroutine baru tetap berjalan dalam satu worker yang sama.

Scheduling: Kapan Coroutine Berpindah

Coroutine berpindah eksekusi di yield point. Contoh paling jelas adalah menunggu I/O:

Dua coroutine berbagi satu worker
use Swoole\Coroutine;
 
Coroutine::create(function () {
    sleep(1);               // hook ke non-blocking
    echo "A selesai\n";
});
 
Coroutine::create(function () {
    sleep(1);               // hook ke non-blocking
    echo "B selesai\n";
});

Tanpa coroutine, dua sleep(1) berurutan butuh 2 detik. Dengan coroutine, keduanya berjalan paralel dalam satu worker dan selesai dalam ~1 detik — karena sleep() di-hook menjadi Swoole\Coroutine\System::sleep() yang menunda coroutine, bukan memblokir worker. Inilah coroutine hooking: fungsi-fungsi blocking standar PHP (sleep, curl, stream, mysqli, dll) secara transparan diubah menjadi non-blocking.

Note

Tidak semua fungsi ter-hook. Fungsi murni CPU (looping, string processing, hashing) tidak bisa "diputar" — ia memblokir worker sampai selesai. Untuk pekerjaan CPU-heavy, gunakan task worker (episode 11). Hook aktif untuk: sleep, curl, stream, PDO/mysqli (via mysqlnd), Redis, file_get_contents, dan client Swoole.

Kode Sinkron, Eksekusi Async

Inilah keajaiban coroutine — bandingkan dua gaya menulis HTTP call:

Callback hell (gaya async klasik)
$client->on('response', function ($resp) use ($client) {
    if ($resp->statusCode === 200) {
        $client2->on('response', function ($resp2) {
            // makin dalam, makin kacau
        });
        $client2->get('/api/b');
    }
});
$client->get('/api/a');
Coroutine (gaya sinkron)
use Swoole\Coroutine;
 
Coroutine::create(function () {
    $a = new Coroutine\Http\Client('api-a.local');
    $a->get('/endpoint');
 
    $b = new Coroutine\Http\Client('api-b.local');
    $b->get('/endpoint');
 
    echo $a->body, $b->body;
});

Kode kedua terlihat persis seperti PHP sinkron biasa — tetapi tidak memblokir worker, dan tetap bisa dipakai bersamaan dengan kode lain. Keterbacaan naik drastis, kesalahan logic berkurang.

defer: Pembersihan yang Dijamin

defer() mendaftarkan fungsi yang dijalankan saat coroutine selesai (normal maupun exception):

defer untuk cleanup
use Swoole\Coroutine;
 
Coroutine::create(function () {
    Coroutine::defer(function () {
        echo "Bersih-bersih\n";
    });
 
    $res = Coroutine\Http\get('https://example.com/api');
    echo $res->getBody();
});

Analoginya try/finally dalam satu baris. Sempurna untuk melepas lock, menutup file, atau menghapus temporary state — menjamin tidak ada kebocoran resource ketika coroutine mati di tengah jalan.

Context dan Batasannya

Beberapa hal yang wajib diketahui sebelum coroutine dipakai produksi:

  • Tidak ada global state per coroutine — seperti thread, coroutine tidak otomatis memiliki "global" sendiri. Variabel harus di-pass lewat closure atau context object.
  • Context coroutine — kode yang butuh context (client coroutine, channel) harus berjalan di dalam Coroutine::create. Di server Swoole, setiap event callback (on('Request'), on('Message')) otomatis berjalan dalam context coroutine bila enable_coroutine aktif.
  • Stack memory — tiap coroutine punya stack sendiri (biasanya 2 MB default, bisa diatur stack_size). Puluhan ribu coroutine = puluhan GB virtual memory — diatur via memory_limit dan stack_size.
  • Jangan gabung callback blocking + coroutine — memanggil sleep() versi blocking di dalam coroutine akan memblokir seluruh worker.

Common Pitfalls

MasalahPenyebabSolusi
Worker hangsleep()/file_get_contents tidak ter-hook (disable hook)Cek swoole build options, atau gunakan Coroutine\System
"Coroutine has already been created"Coroutine::create dari luar context serverPastikan dipanggil di dalam event callback / coroutine lain
Memory naik terusStack coroutine membengkak / coroutine bocorSet stack_size, selidiki coroutine yang tidak pernah selesai
Race conditionState bersama diubah banyak coroutine dalam satu workerGunakan Channel/Lock (episode 8 & 12)

Penutup

Pada episode 7 ini, kalian telah memahami fondasi coroutine di Swoole.

Inti yang harus dibawa pulang:

  • Coroutine = kode sinkron yang dieksekusi non-blocking di dalam satu worker.
  • Coroutine::create() (atau go()) meluncurkan coroutine; defer() menjamin cleanup.
  • Coroutine berpindah di yield point — umumnya saat operasi I/O yang di-hook.
  • Hook mengubah fungsi blocking standar PHP menjadi non-blocking secara transparan.
  • CPU-bound work tetap harus dipindahkan ke task worker.

Di episode 8 selanjutnya, kita belajar berkomunikasi antar coroutine: Channels & WaitGroup — mengirim data antar coroutine dengan Coroutine\Channel dan menunggu banyak task selesai secara parallel. Sampai jumpa di episode 8!