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:

go
ch := make(chan int) // channel yang mengangkut nilai int
ch <- 42             // kirim nilai 42 ke channel
v := <-ch            // terima nilai dari channel ke variabel v

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

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

text
main mengirim: 42
goroutine menerima: 42
main menerima konfirmasi: siap
program selesai

Mari kita telaah baris demi baris program di atas:

  1. Baris 6–7 (make(chan ...)): membuat dua channel unbuffered — ch mengangkut int, konfirmasi mengangkut string.
  2. Baris 9–13 (go func): goroutine baru pertama-tama menunggu di <-ch; goroutine ini belum mencetak apa pun sebelum main mengirim.
  3. Baris 15 (fmt.Println): main mencetak baris pertama — dijamin menjadi baris pertama output karena goroutine masih terblokir menunggu data.
  4. Baris 16 (ch <- 42): pengiriman berhenti sampai goroutine penerima siap; begitu jabat tangan terjadi, goroutine menerima 42 dan mencetaknya sementara main langsung bergerak.
  5. Baris 18 (<-konfirmasi): main memblokir menunggu konfirmasi, sehingga goroutine leluasa mencetak pesannya tanpa bisa tergeser posisi oleh main.
  6. Baris 19–20: setelah konfirmasi diterima, main mencetak dua baris penutup; urutan keempat baris output dengan demikian pasti setiap kali program dijalankan.

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:

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

text
menerima: 1
menerima: 2
menerima: 3
setelah close: nilai=0 ok=false

Mari kita telaah baris demi baris program di atas:

  1. Baris 10–15 (go func produsen): mengirim nilai 1, 2, 3 secara berurutan ke ch, lalu menutup channel; channel unbuffered menjamin nilai tiba dalam urutan pengiriman (FIFO).
  2. Baris 18–23 (go func konsumen): range ch menerima nilai selama channel terbuka, mencetak tiap nilai, lalu keluar dari loop begitu channel tertutup dan selesai dikosongkan.
  3. Baris 25 (<-selesai): main menunggu konsumen menyelesaikan seluruh pekerjaannya sebelum mencetak baris ringkasan.
  4. Baris 27–28 (v, ok := <-ch): menerima dari channel yang sudah ditutup dan kosong menghasilkan zero value 0 dengan ok=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:

go
package main

func main() {
	ch := make(chan int)
	ch <- 42 // tidak ada penerima: deadlock
}

Jalankan berkas tersebut:

bash
go run error-deadlock.go

Output yang Dihasilkan:

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

channel-demo.go

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

error-deadlock.go

Unduh berkas
go
package main

func main() {
	ch := make(chan int)
	ch <- 42 // tidak ada penerima: deadlock
}