Belajar komunikasi antar coroutine dengan Coroutine\Channel untuk mengirim data secara buffered maupun unbuffered, serta WaitGroup untuk pola parallel fan-out yang menunggu banyak task selesai sebelum lanjut.

Setelah di episode 7 kita memahami bagaimana coroutine berjalan dan berpindah — pada episode kali ini kita belajar membuat coroutine saling bicara. Di dunia nyata, coroutine jarang berjalan sendiri-sendiri: satu coroutine mengambil data, yang lain memprosesnya, yang lain menulis ke database. Tanpa mekanisme komunikasi, semua harus di-jejalkan ke satu fungsi raksasa.
Mengapa episode ini penting? Karena Coroutine\Channel dan WaitGroup adalah dua tool yang mengubah coroutine dari "kemampuan" menjadi "pola desain". Keduanya muncul di hampir setiap aplikasi Swoole produksi — dari pipeline pemrosesan antrian sampai aggregator API.
Swoole\Coroutine\Channel adalah struktur FIFO untuk mengirim data antar coroutine dalam satu worker. Analogi dunia nyata: pipet di restoran — kokinya memasukkan piring ke pipet, pelayan mengambilnya di sisi lain.
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
Coroutine::create(function () {
$channel = new Channel(10); // buffer 10 item
Coroutine::create(function () use ($channel) {
$channel->push('data-1');
$channel->push('data-2');
});
Coroutine::create(function () use ($channel) {
echo $channel->pop() . "\n";
echo $channel->pop() . "\n";
});
});Metode kunci:
| Metode | Fungsi |
|---|---|
new Channel($size) | Buat channel dengan buffer $size item |
push($data) | Masukkan data; menunda bila buffer penuh |
pop($timeout) | Ambil data; menunda bila kosong (sampai $timeout) |
close() | Tutup channel, semua pop yang menunggu dibangunkan |
stats() | Statistik ukuran & jumlah coroutine yang menunggu |
length() | Jumlah item saat ini |
new Channel(10)): push tidak menunda selama buffer belum penuh — producer bisa melaju lebih dulu.new Channel(0)): push dan pop harus bertemu langsung; salah satu harus menunggu yang lain. Ini mengubah channel menjadi sinkronisasi yang ketat — push menunggu sampai ada pop.Pilih buffered saat ingin "mengecoh" beban (producer cepat, consumer lambat); pilih unbuffered saat dua coroutine harus berjalan seiring.
Pola paling umum memakai channel: beberapa coroutine memproduksi, satu atau lebih coroutine mengkonsumsi:
<?php
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
$channel = new Channel(10);
// Producer: mengambil halaman dari 3 sumber
foreach (['sumber-a', 'sumber-b', 'sumber-c'] as $source) {
Coroutine::create(function () use ($channel, $source) {
$html = Coroutine\Http\get("https://$source.example.com");
$channel->push(['source' => $source, 'html' => $html->getBody()]);
});
}
// Consumer: satu coroutine memproses hasil
Coroutine::create(function () use ($channel) {
for ($i = 0; $i < 3; $i++) {
$item = $channel->pop(5); // tunggu maksimal 5 detik
if ($item === false) {
break; // timeout — hentikan
}
echo "Proses {$item['source']}: " . strlen($item['html']) . " bytes\n";
}
});pop(5) mengembalikan false bila channel kosong lebih dari 5 detik — pola yang wajib dipakai agar consumer tidak menggantung selamanya saat producer gagal.
Swoole\Coroutine\WaitGroup menyelesaikan masalah klasik: "jalankan N task parallel, lanjutkan setelah semuanya selesai". API-nya mirip sync.WaitGroup di Go:
<?php
use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;
$wg = new WaitGroup();
$results = [];
foreach (['service-a', 'service-b', 'service-c'] as $svc) {
$wg->add();
Coroutine::create(function () use ($wg, $svc, &$results) {
try {
$resp = Coroutine\Http\get("http://$svc/api/data");
$results[$svc] = $resp->getBody();
} finally {
$wg->done();
}
});
}
$wg->wait(); // menunggu SEMUA selesai
echo "Semua selesai: " . count($results) . " service\n";Tiga aturan emas WaitGroup:
add() dipanggil sebelum coroutine dibuat (menambah penghitung).done() dipanggil tepat sekali per coroutine — taruh di finally agar tetap dieksekusi walau exception.wait() memblokir coroutine saat ini sampai penghitung mencapai nol.WaitGroup + Channel adalah kombinasi klasik fan-out (pecah jadi banyak task) dan fan-in (kumpulkan semua hasil):
Kombinasi ini kalian pakai lagi di episode 9 untuk aggregator API yang memanggil beberapa upstream secara parallel.
| Masalah | Penyebab | Solusi |
|---|---|---|
| Coroutine menggantung selamanya | pop() tanpa timeout dan channel tidak pernah diisi | Selalu set timeout pop($t), handle false |
| Data hilang | done() sebelum hasil disimpan | Simpan hasil dulu, baru done() |
| Deadlock | Dua channel saling menunggu (push dan pop silang) | Gunakan satu arah aliran data, atau timeout |
WaitGroup done dua kali | done() di luar finally dan exception melewatinya | Pindahkan done() ke finally |
Pada episode 8 ini, kalian telah menguasai komunikasi dan sinkronisasi antar coroutine.
Inti yang harus dibawa pulang:
Coroutine\Channel = jalur FIFO antar coroutine; push/pop dengan timeout.WaitGroup = add() → jalankan coroutine → done() di finally → wait().Di episode 9 selanjutnya, kita memakai semua ini untuk hal nyata: coroutine clients — HTTP, TCP/UDP, MySQL, dan Redis, plus membangun aggregator API yang memanggil banyak upstream secara parallel. Sampai jumpa di episode 9!