Topik ini memperkenalkan channel tanpa penyangga, yaitu jalur komunikasi bertipe yang sekaligus berperan sebagai alat sinkronisasi antar goroutine. Setelah membacanya, nilai dapat dikirim dari satu goroutine ke goroutine lain dengan aman, channel dapat ditutup sebagai tanda selesai, dan kebuntuan (deadlock) yang paling sering muncul dapat dikenali sejak awal.
Mengirim dan menerima nilai#
Channel adalah jalur komunikasi bertipe (channel), yaitu pipa tempat satu goroutine mengirim nilai dan goroutine lain menerimanya dalam urutan aslinya. Channel dibuat dengan make(chan T) untuk nilai bertipe T, dan operasinya ditulis dengan operator panah <-. Misalnya, keluar <- 24 mengirim nilai, sedangkan suhu := <-keluar menerimanya. Channel yang dibuat tanpa argumen kedua tidak memiliki penyangga (unbuffered), artinya tidak ada tempat penampungan di tengah jalan. Pengirim dan penerima harus bertemu, dan pertukaran terjadi pada saat keduanya siap.
Program berikut meniru dua sensor suhu yang membaca secara bersamaan, lalu melaporkan hasilnya ke main. Setiap sensor mendapat channel sendiri agar urutan pencetakan tidak bergantung pada urutan goroutine selesai.
package main
import (
"fmt"
"time"
)
func bacaSensor(nama string, suhu int, keluar chan string) {
time.Sleep(10 * time.Millisecond)
keluar <- fmt.Sprintf("%s: %d derajat Celsius", nama, suhu)
}
func main() {
dapur := make(chan string)
gudang := make(chan string)
go bacaSensor("dapur", 24, dapur)
go bacaSensor("gudang", 19, gudang)
fmt.Println("dua sensor mulai membaca")
fmt.Println(<-dapur)
fmt.Println(<-gudang)
}dua sensor mulai membaca
dapur: 24 derajat Celsius
gudang: 19 derajat CelsiusFungsi bacaSensor meniru pembacaan yang memakan waktu dengan jeda sepuluh milidetik, lalu mengirim hasilnya ke channel masing-masing. Penerimaan <-dapur menahan main sampai sensor dapur benar-benar mengirim, dan baris berikutnya menunggu channel gudang dengan cara yang sama. Karena setiap sensor punya channel sendiri, urutan pencetakan ditentukan oleh urutan penerimaan pada kode, bukan oleh urutan goroutine selesai. Dengan begitu, persoalan urutan cetak yang tersisa dari topik goroutine terjawab dengan rapi.
Alur pertukarannya tampak pada diagram berikut. Kedua pihak mengajukan operasinya ke channel, dan nilai berpindah hanya setelah keduanya bertemu.
Menutup channel sebagai tanda selesai#
Penerimaan yang menunggu membutuhkan tanda kapan berhenti. Fungsi close(antrean) menutup channel dari sisi pengirim, sehingga penerima tahu bahwa tidak akan ada nilai lagi. Bentuk perulangan for nilai := range antrean menerima nilai satu per satu sampai channel ditutup, lalu berhenti dengan sendirinya. Bentuk penerimaan lain, nilai, buka := <-antrean, mengembalikan false pada variabel kedua setelah channel ditutup, sedangkan nilai pertamanya berupa zero value tipe channel tersebut.
Program berikut menyerahkan tugas piket lewat satu channel, lalu menutupnya setelah daftar habis.
package main
import "fmt"
func main() {
antrean := make(chan string)
go func() {
antrean <- "menyapu kelas"
antrean <- "membuang sampah"
close(antrean)
}()
for tugas := range antrean {
fmt.Println("mengerjakan", tugas)
}
fmt.Println("antrean ditutup, tidak ada tugas lagi")
}mengerjakan menyapu kelas
mengerjakan membuang sampah
antrean ditutup, tidak ada tugas lagiKeluaran di atas memperlihatkan dua tugas diterima dalam urutan pengirimannya, lalu perulangan berhenti setelah close dijalankan. Penutupan sekaligus menggantikan penungguan yang sembarangan seperti jeda waktu, karena penerima berhenti tepat ketika datanya habis. Dengan pola ini, satu goroutine pekerja dapat mengerjakan seluruh isi antrean tanpa mengetahui jumlah tugasnya di muka. Satu aturan menyertainya, yaitu channel yang sudah ditutup boleh diterima tetapi tidak boleh dikirim lagi, karena pengiriman setelah penutupan memicu panic.
Saling menunggu dan deadlock#
Pada channel tanpa penyangga, pengiriman tanpa penerima akan menunggu selamanya. Apabila tidak ada goroutine lain yang hidup untuk menerima, runtime Go mendeteksi keadaan itu dan menghentikan program dengan pesan fatal error: all goroutines are asleep - deadlock!. Keadaan semacam ini disebut deadlock, yaitu kondisi semua goroutine saling menunggu tanpa ada yang dapat maju. Pesan tersebut bukan kerusakan acak, melainkan diagnosis yang tepat atas keadaan program.
Potongan berikut menggambarkan kekeliruan itu, dan bloknya tidak dijalankan karena pasti berakhir dengan deadlock.
// tidak-dijalankan
package main
func main() {
hasil := make(chan int)
hasil <- 7 // menunggu penerima yang tidak akan datang
}Kesalahan sejenis paling sering muncul ketika kirim dan terima ditaruh berurutan pada goroutine yang sama. Pengiriman pada channel tanpa penyangga tidak selesai sebelum ada penerima, sehingga baris penerimaan di bawahnya tidak pernah sempat dieksekusi. Apabila penerimaan dipulutkan sebagai goroutine terpisah, pengiriman menemukan pasangannya dan program berlanjut seperti pada program dua sensor di atas.
Kesalahan umum#
- Mengira urutan pemulutan goroutine menentukan urutan penerimaan. Dua goroutine yang berbagi satu channel dapat menyelesaikan pengirimannya dalam urutan apa pun, sehingga hasil yang diterima tertukar. Berikan satu channel khusus untuk setiap pengirim, atau kumpulkan hasilnya lebih dahulu sebelum dicetak.
- Menerima dari channel tanpa pengirim sama sekali. Penerimaan menunggu tanpa batas, dan apabila tidak ada goroutine lain yang hidup, program berakhir deadlock. Mulutkan goroutine pengirimnya sebelum menerima.
- Menutup channel dua kali atau menutupnya dari sisi penerima. Keduanya berakhir panic
close of closed channelatau ditolak kompiler, sehingga penutupan hanya boleh dilakukan pengirim.
Ringkasan#
- Channel tanpa penyangga mempertemukan pengirim dan penerima, sehingga pengiriman sekaligus menjadi titik sinkronisasi.
closedari sisi pengirim menandai akhir data, danrangeserta bentuknilai, buka := <-antreanmemanfaatkan tanda itu.- Penerimaan dari channel yang sudah ditutup tidak menunggu dan mengembalikan zero value.
- Pengiriman tanpa penerima pada goroutine yang sama selalu berakhir deadlock yang dideteksi runtime.
Referensi#
- A Tour of Go: Concurrency — Go Documentation — Latihan resmi channel, termasuk penutupan dan perulangan range.
- Effective Go — Go Documentation — Bagian resmi tentang channel sebagai alat sinkronisasi dan komunikasi.
- package builtin — Go — Dokumentasi resmi fungsi close beserta akibatnya.