Goroutine membelah pekerjaan, tetapi belahan pekerjaan tetap perlu bertukar data. Cara klasik adalah shared memory: semua goroutine menulis ke variabel yang sama lalu berlomba menguncinya — rawan deadlock dan data race. Go membalik pendekatan itu dengan slogan “jangan berkomunikasi dengan berbagi memori; berbagi memorilah dengan berkomunikasi”. Channel adalah saluran bertipe yang menjadi satu-satunya jalur kirim-terima antar goroutine. Chapter ini membahas pembuatan channel, sifat blocking kirim dan terima, penutupan channel, serta deadlock yang muncul ketika tidak ada lagi goroutine yang bisa maju.
Membuat dan Menggunakan Channel #
Channel dibuat dengan make dan bertipe chan T dengan T tipe data yang diangkut:
ch := make(chan int) // channel yang mengangkut nilai int
ch <- 42 // kirim nilai 42 ke channel
v := <-ch // terima nilai dari channel ke variabel vArah panah <- menunjukkan arah aliran data: ch <- 42 berarti data masuk ke ch, sedangkan v := <-ch berarti data keluar dari ch menuju v. Channel bertipe terbatas pada satu tipe; channel chan int tidak bisa mengangkut string, dan compiler menolak pencampuran tipe saat kompilasi.
Unbuffered Channel Menyinkronkan Dua Goroutine #
Channel hasil make(chan int) tanpa angka kapasitas adalah unbuffered. Pada channel ini, kirim dan terima berjabat tangan: statement ch <- 42 memblokir goroutine pengirim sampai ada goroutine lain yang siap menerima, dan sebaliknya penerima memblokir sampai ada pengirim. Perjumpaan dua arah inilah yang disebut rendezvous. Model memori Go (memory model) menjamin semua tulisan yang dilakukan pengirim sebelum kirim terlihat oleh penerima setelah terima — sinkronisasi sekaligus transfer data.
Berkas channel-demo.go memakai sifat tersebut untuk menjaga urutan cetak yang pasti:
package main
import "fmt"
func main() {
ch := make(chan int)
konfirmasi := make(chan string)
go func() {
nilai := <-ch
fmt.Println("goroutine menerima:", nilai)
konfirmasi <- "siap"
}()
fmt.Println("main mengirim: 42")
ch <- 42
pesan := <-konfirmasi
fmt.Println("main menerima konfirmasi:", pesan)
fmt.Println("program selesai")
}Output yang Dihasilkan:
main mengirim: 42
goroutine menerima: 42
main menerima konfirmasi: siap
program selesaiMari kita telaah baris demi baris program di atas:
- Baris 6–7 (
make(chan ...)): membuat dua channel unbuffered —chmengangkutint,konfirmasimengangkutstring. - Baris 9–13 (
go func): goroutine baru pertama-tama menunggu di<-ch; goroutine ini belum mencetak apa pun sebelummainmengirim. - Baris 15 (
fmt.Println):mainmencetak baris pertama — dijamin menjadi baris pertama output karena goroutine masih terblokir menunggu data. - Baris 16 (
ch <- 42): pengiriman berhenti sampai goroutine penerima siap; begitu jabat tangan terjadi, goroutine menerima42dan mencetaknya sementaramainlangsung bergerak. - Baris 18 (
<-konfirmasi):mainmemblokir menunggu konfirmasi, sehingga goroutine leluasa mencetak pesannya tanpa bisa tergeser posisi olehmain. - Baris 19–20: setelah konfirmasi diterima,
mainmencetak dua baris penutup; urutan keempat baris output dengan demikian pasti setiap kali program dijalankan.
Menutup Channel dan range #
Channel dapat ditutup dengan close(ch). Penutupan memberi tanda “tidak ada data lagi” kepada penerima: receive berikutnya mengembalikan zero value dari tipe channel, dan bentuk dua nilai v, ok := <-ch memberi ok == false sebagai bukti channel sudah ditutup. Loop for v := range ch secara otomatis menerima nilai sampai channel ditutup — pola standar produsen-konsumen.
Berkas channel-close-demo.go memperlihatkan ketiganya:
package main
import "fmt"
func main() {
ch := make(chan int)
selesai := make(chan bool)
// produsen: kirim tiga nilai lalu tutup channel
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
// konsumen: terima nilai sampai channel ditutup
go func() {
for v := range ch {
fmt.Println("menerima:", v)
}
selesai <- true
}()
<-selesai
v, ok := <-ch
fmt.Printf("setelah close: nilai=%d ok=%v\n", v, ok)
}Output yang Dihasilkan:
menerima: 1
menerima: 2
menerima: 3
setelah close: nilai=0 ok=falseMari kita telaah baris demi baris program di atas:
- Baris 10–15 (
go funcprodusen): mengirim nilai 1, 2, 3 secara berurutan kech, lalu menutup channel; channel unbuffered menjamin nilai tiba dalam urutan pengiriman (FIFO). - Baris 18–23 (
go funckonsumen):range chmenerima nilai selama channel terbuka, mencetak tiap nilai, lalu keluar dari loop begitu channel tertutup dan selesai dikosongkan. - Baris 25 (
<-selesai):mainmenunggu konsumen menyelesaikan seluruh pekerjaannya sebelum mencetak baris ringkasan. - Baris 27–28 (
v, ok := <-ch): menerima dari channel yang sudah ditutup dan kosong menghasilkan zero value0denganok=false— bukti visual bahwa receive tidak memblokir setelah close.
Deadlock: Semua Goroutine Tertidur #
Ketika seluruh goroutine saling menunggu dan tidak ada yang bisa maju, runtime Go mendeteksi kebuntuan total dan menghentikan program. Kasus paling sederhana: mengirim ke channel unbuffered tanpa penerima sama sekali, seperti pada berkas error-deadlock.go:
package main
func main() {
ch := make(chan int)
ch <- 42 // tidak ada penerima: deadlock
}Jalankan berkas tersebut:
go run error-deadlock.goOutput yang Dihasilkan:
fatal error: all goroutines are asleep - deadlock!Runtime mencetak fatal error diikuti stack trace tiap goroutine yang tertidur (menunjuk baris ch <- 42), lalu mengakhiri program dengan exit code 2. Stack trace lengkapnya dihilangkan di sini karena panjang dan berisi detail internal runtime; baris pertama inilah yang perlu Anda kenali. Solusinya selalu sama: pastikan ada goroutine lain yang siap menerima sebelum mengirim, atau beri channel kapasitas buffer seperti pada chapter Buffered dan Unbuffered Channel
.
Referensi #
- Channel types — Spesifikasi Go — aturan resmi tipe, kirim, terima, dan close.
- The Go Memory Model — jaminan happens-before akibat komunikasi channel.
- Concurrency — Effective Go — filosofi berbagi memori dengan berkomunikasi.
Channel unbuffered memaksa pengirim dan penerima bertemu pada waktu yang sama. Ketika pertemuan langsung tidak diinginkan — misalnya produsen ingin terus bekerja tanpa menunggu — Go menyediakan channel berbuffer yang kita bahas di chapter selanjutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
channel-close-demo.go
Unduh berkaspackage main
import "fmt"
func main() {
ch := make(chan int)
selesai := make(chan bool)
// produsen: kirim tiga nilai lalu tutup channel
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
// konsumen: terima nilai sampai channel ditutup
go func() {
for v := range ch {
fmt.Println("menerima:", v)
}
selesai <- true
}()
<-selesai
v, ok := <-ch
fmt.Printf("setelah close: nilai=%d ok=%v\n", v, ok)
}
channel-demo.go
Unduh berkaspackage main
import "fmt"
func main() {
ch := make(chan int)
konfirmasi := make(chan string)
go func() {
nilai := <-ch
fmt.Println("goroutine menerima:", nilai)
konfirmasi <- "siap"
}()
fmt.Println("main mengirim: 42")
ch <- 42
pesan := <-konfirmasi
fmt.Println("main menerima konfirmasi:", pesan)
fmt.Println("program selesai")
}
error-deadlock.go
Unduh berkaspackage main
func main() {
ch := make(chan int)
ch <- 42 // tidak ada penerima: deadlock
}