Channel unbuffered menuntut jabat tangan pengirim-penerima yang ketat, tetapi ketatnya mengharuskan kedua pihak hadir pada saat yang sama. Produsen yang lebih cepat dari konsumen akan bolak-balik berhenti menunggu, dan program yang menuntut pertemuan serentak bisa jadi kaku. Channel buffered memberi ruang antrean di tengah: pengirim boleh melanjutkan kerja selama antrean belum penuh. Chapter ini mengamati perilaku buffer secara langsung lewat len dan cap, lalu menyusun kaidah memilih antara buffered dan unbuffered.
Channel Berbuffer #
Kapasitas buffer ditetapkan sebagai argumen kedua make:
ch := make(chan int, 3) // buffer berisi maksimal 3 nilaiPerilaku kirim dan terima berubah menjadi bertahap. Pengiriman ch <- v memblokir hanya ketika buffer sudah penuh; selama masih ada ruang, pengirim menaruh nilai di antrean dan langsung melanjutkan pekerjaan. Sebaliknya, penerimaan <-ch memblokir hanya ketika buffer kosong. Nilai keluar dari buffer dengan urutan yang sama seperti saat masuk (FIFO), sehingga urutan pesan tetap terjaga meski kedua pihak bekerja dengan irama berbeda.
Mengamati Buffer dengan len dan cap #
Fungsi cap(ch) melaporkan kapasitas buffer, sedangkan len(ch) melaporkan jumlah nilai yang sedang mengantre di dalamnya. Berkas buffered-channel-demo.go memakai keduanya untuk membongkar isi buffer langkah demi langkah:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
fmt.Println("kapasitas :", cap(ch))
fmt.Println("isi awal :", len(ch))
ch <- 10
ch <- 20
fmt.Println("isi setelah 2 kirim :", len(ch))
fmt.Println("menerima :", <-ch)
fmt.Println("isi setelah 1 terima :", len(ch))
ch <- 30
ch <- 40
fmt.Println("isi setelah 4 total :", len(ch))
close(ch)
for v := range ch {
fmt.Println("drain :", v)
}
fmt.Println("isi setelah drain :", len(ch))
}Output yang Dihasilkan:
kapasitas : 3
isi awal : 0
isi setelah 2 kirim : 2
menerima : 10
isi setelah 1 terima : 1
isi setelah 4 total : 3
drain : 20
drain : 30
drain : 40
isi setelah drain : 0Mari kita telaah baris demi baris program di atas:
- Baris 6 (
make(chan int, 3)): membuat channel berbuffer tiga; seluruh program hanya dijalankanmainsehingga pengamatanlendancapbebas dari gangguan goroutine lain. - Baris 8–9: kapasitas terbaca
3dan isi awal0— buffer baru dibuat dalam keadaan kosong. - Baris 11–14: dua pengiriman masuk tanpa ada penerima dan tanpa memblokir karena buffer masih menyisakan ruang;
lennaik menjadi2, bukti nilai menunggu di antrean. - Baris 15–16: satu penerimaan mengeluarkan
10— nilai pertama yang masuk keluar lebih dulu (FIFO) — danlenturun menjadi1. - Baris 18–20: dua pengiriman baru menaikkan isi menjadi
3; di titik ini buffer penuh, sehingga kirim kelima akan memblokir sampai ada penerima. - Baris 22–26 (
close+range): penutupan channel lalurangemenguras sisa antrean20,30,40secara berurutan; setelah kosong,lenkembali0.
Kapan Buffered, Kapan Unbuffered #
Pilihan kapasitas adalah keputusan desain, bukan preferensi gaya. Tabel berikut merangkum kaidah praktisnya:
| Aspek | Unbuffered make(chan T) | Buffered make(chan T, n) |
|---|---|---|
| Karakter kirim-terima | Jabat tangan: kirim selesai bersama terima | Terpisah: kirim selesai saat masuk antrean |
| Kekuatan utama | Sinkronisasi dan transfer kepemilikan data | Kelonggaran irama kerja produsen-konsumen |
| Cocok untuk | Handshake, kunci selesai satu langkah, hasil tunggal | Pipa produksi berkelanjutan, perata beban burst |
| Jebakan | Deadlock bila pasangan tidak siap | Masalah sinkronisasi tertunda dan tersembunyi |
Unbuffered tepat ketika kirim itu sendiri adalah sinkronisasi: pemanggil menunggu pekerjaan diterima, atau kepemilikan data berpindah tangan tepat pada momen kirim. Buffered tepat ketika kedua pihak berproduksi dan mengonsumsi secara berkelanjutan dengan kecepatan berbeda, seperti pipa (pipeline) tahap demi tahap.
Buffer sebagai Pipa Produksi #
Berkas buffered-pipeline-demo.go menampilkan peran buffer di dunia nyata: produsen dan konsumen bekerja dengan durasi berbeda tanpa saling menunggu di setiap nilai:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 4)
// produsen: mengisi buffer tanpa menunggu konsumen
go func() {
for i := 1; i <= 4; i++ {
time.Sleep(20 * time.Millisecond) // simulasi produksi
ch <- i
}
close(ch)
}()
// konsumen: main mengosongkan buffer sambil bekerja
for v := range ch {
time.Sleep(10 * time.Millisecond) // simulasi pemrosesan
fmt.Println("konsumen menerima:", v)
}
fmt.Println("selesai")
}Output yang Dihasilkan:
konsumen menerima: 1
konsumen menerima: 2
konsumen menerima: 3
konsumen menerima: 4
selesaiMari kita telaah baris demi baris program di atas:
- Baris 9 (
make(chan int, 4)): buffer berkapasitas empat memuat seluruh hasil produksi sehingga produsen bebas menyelesaikan semua kerjanya lebih dulu. - Baris 12–18 (
go funcprodusen): tiap nilai dikirim setelah simulasi kerja; duatime.Sleepdi program ini hanya mensimulasikan durasi kerja, bukan mengatur urutan output — urutan dijamin oleh channel FIFO dan penutupanclose. - Baris 21–24 (
for v := range ch): konsumen memproses tiap nilai lalu mencetaknya; nilai tercetak berurutan 1 sampai 4 meski irama kedua pihak berbeda. - Baris 26: loop
rangeberakhir otomatis saat channel tertutup dan kosong, menandai seluruh pipa tuntas.
Pembahasan lanjutan tentang rangkaian pipa multi-tahap tersedia di chapter Pipeline . Sebelum itu, kita perlu satu alat pengaman lain: membatasi arah channel agar fungsi hanya boleh mengirim atau hanya boleh menerima — materi chapter Channel Direction .
Referensi #
- Channel types — Spesifikasi Go — aturan kapasitas dan perilaku blokir channel.
- Making slices, maps and channels — Spesifikasi Go
— bentuk
makedengan argumen kapasitas. - Go Concurrency Patterns: Pipelines — The Go Blog — pola pipa dengan buffered channel.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buffered-channel-demo.go
Unduh berkaspackage main
import "fmt"
func main() {
ch := make(chan int, 3)
fmt.Println("kapasitas :", cap(ch))
fmt.Println("isi awal :", len(ch))
ch <- 10
ch <- 20
fmt.Println("isi setelah 2 kirim :", len(ch))
fmt.Println("menerima :", <-ch)
fmt.Println("isi setelah 1 terima :", len(ch))
ch <- 30
ch <- 40
fmt.Println("isi setelah 4 total :", len(ch))
close(ch)
for v := range ch {
fmt.Println("drain :", v)
}
fmt.Println("isi setelah drain :", len(ch))
}
buffered-pipeline-demo.go
Unduh berkaspackage main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 4)
// produsen: mengisi buffer tanpa menunggu konsumen
go func() {
for i := 1; i <= 4; i++ {
time.Sleep(20 * time.Millisecond) // simulasi produksi
ch <- i
}
close(ch)
}()
// konsumen: main mengosongkan buffer sambil bekerja
for v := range ch {
time.Sleep(10 * time.Millisecond) // simulasi pemrosesan
fmt.Println("konsumen menerima:", v)
}
fmt.Println("selesai")
}