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:

go
ch := make(chan int, 3) // buffer berisi maksimal 3 nilai

Perilaku 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:

go
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:

text
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    : 0

Mari kita telaah baris demi baris program di atas:

  1. Baris 6 (make(chan int, 3)): membuat channel berbuffer tiga; seluruh program hanya dijalankan main sehingga pengamatan len dan cap bebas dari gangguan goroutine lain.
  2. Baris 8–9: kapasitas terbaca 3 dan isi awal 0 — buffer baru dibuat dalam keadaan kosong.
  3. Baris 11–14: dua pengiriman masuk tanpa ada penerima dan tanpa memblokir karena buffer masih menyisakan ruang; len naik menjadi 2, bukti nilai menunggu di antrean.
  4. Baris 15–16: satu penerimaan mengeluarkan 10 — nilai pertama yang masuk keluar lebih dulu (FIFO) — dan len turun menjadi 1.
  5. Baris 18–20: dua pengiriman baru menaikkan isi menjadi 3; di titik ini buffer penuh, sehingga kirim kelima akan memblokir sampai ada penerima.
  6. Baris 22–26 (close + range): penutupan channel lalu range menguras sisa antrean 20, 30, 40 secara berurutan; setelah kosong, len kembali 0.

Kapan Buffered, Kapan Unbuffered #

Pilihan kapasitas adalah keputusan desain, bukan preferensi gaya. Tabel berikut merangkum kaidah praktisnya:

AspekUnbuffered make(chan T)Buffered make(chan T, n)
Karakter kirim-terimaJabat tangan: kirim selesai bersama terimaTerpisah: kirim selesai saat masuk antrean
Kekuatan utamaSinkronisasi dan transfer kepemilikan dataKelonggaran irama kerja produsen-konsumen
Cocok untukHandshake, kunci selesai satu langkah, hasil tunggalPipa produksi berkelanjutan, perata beban burst
JebakanDeadlock bila pasangan tidak siapMasalah 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:

go
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:

text
konsumen menerima: 1
konsumen menerima: 2
konsumen menerima: 3
konsumen menerima: 4
selesai

Mari kita telaah baris demi baris program di atas:

  1. Baris 9 (make(chan int, 4)): buffer berkapasitas empat memuat seluruh hasil produksi sehingga produsen bebas menyelesaikan semua kerjanya lebih dulu.
  2. Baris 12–18 (go func produsen): tiap nilai dikirim setelah simulasi kerja; dua time.Sleep di program ini hanya mensimulasikan durasi kerja, bukan mengatur urutan output — urutan dijamin oleh channel FIFO dan penutupan close.
  3. Baris 21–24 (for v := range ch): konsumen memproses tiap nilai lalu mencetaknya; nilai tercetak berurutan 1 sampai 4 meski irama kedua pihak berbeda.
  4. Baris 26: loop range berakhir 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buffered-channel-demo.go

Unduh berkas
go
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))
}

buffered-pipeline-demo.go

Unduh berkas
go
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")
}