Topik ini membahas arah channel, yaitu penanda kirim-saja dan terima-saja pada tipe channel yang membuat peran setiap fungsi diperiksa kompiler. Setelah membacanya, tanda tangan fungsi konkuren dapat menyatakan perannya dengan jelas, dan kekeliruan arah tertangkap sebelum program berjalan.
Peran pada tanda tangan fungsi#
Tipe chan<- int menyatakan channel kirim-saja (send-only), yaitu channel yang hanya boleh menerima pengiriman, sedangkan <-chan int menyatakan channel terima-saja (receive-only) yang hanya boleh diterima. Keduanya bukan channel baru, melainkan sudut pandang yang lebih sempit atas channel yang sama. Channel dua arah yang dibuat dengan make boleh diteruskan ke parameter berarah, sedangkan jalan baliknya ditolak kompiler.
Program berikut membagi pekerjaan pengolah angka menjadi dua tahap. Tahap pertama menghasilkan kuadrat, tahap kedua menjumlahkannya, dan arah channel pada kedua tanda tangan itulah yang membatasi kemampuan masing-masing fungsi.
package main
import "fmt"
func hasilkan(deret []int, keluar chan<- int) {
for _, a := range deret {
keluar <- a * a
}
close(keluar)
}
func jumlahkan(masuk <-chan int, selesai chan<- int) {
total := 0
for a := range masuk {
total += a
}
selesai <- total
}
func main() {
angka := make(chan int)
total := make(chan int)
go hasilkan([]int{2, 4, 6, 8}, angka)
go jumlahkan(angka, total)
fmt.Println("jumlah kuadrat:", <-total)
}jumlah kuadrat: 120Fungsi hasilkan menerima keluar chan<- int, sehingga satu-satunya kemampuannya adalah mengirim dan menutup. Fungsi jumlahkan menerima masuk <-chan int, sehingga satu-satunya kemampuannya adalah menerima sampai channel ditutup. Tanda tangan kedua fungsi itu sekaligus menjadi dokumentasinya, karena pembaca tidak perlu membaca badan fungsinya untuk tahu siapa mengirim dan siapa menerima. Perhatikan pula bahwa close(keluar) sah pada parameter kirim-saja, karena penutupan memang hak pengirim.
Pada program yang lebih besar, pembatasan arah ikut menjaga kebenaran program dengan membatasi kemampuan setiap pihak. Fungsi yang menerima chan<- int tidak mungkin membaca data yang seharusnya dikirim, dan fungsi yang menerima <-chan int tidak mungkin menyelipkan data ke jalur yang bukan haknya. Apabila dibiarkan mungkin terjadi, pelanggaran semacam ini sulit dilacak karena berupa perilaku salah saat berjalan, misalnya pengiriman ke channel yang tidak pernah dibaca siapa pun. Dengan mempersempit arah sejak tanda tangan, kesalahan kelas itu berhenti pada pesan error kompiler.
| Tipe | Mengirim | Menerima | close |
|---|---|---|---|
chan int | boleh | boleh | boleh |
chan<- int | boleh | ditolak | boleh |
<-chan int | ditolak | boleh | ditolak |
Kompiler sebagai pengawas#
Kekeliruan arah tidak menunggu program berjalan. Potongan berikut menolak untuk dikompilasi, dan bloknya tidak dijalankan karena memang bukan program utuh.
// tidak-dijalankan
func periksa(baca <-chan int) {
baca <- 3 // ditolak kompiler
}Pesan kompilernya berbunyi invalid operation: cannot send to receive-only channel, lengkap dengan tipe dan nama variabelnya. Pemeriksaan yang sama berlaku untuk close pada channel terima-saja, yang ditolak dengan pesan serupa. Kedua aturan itu menjaga kontrak sederhana, yaitu hanya pengirim yang menutup dan hanya penerima yang membaca. Kesalahan yang semula berupa perilaku salah saat berjalan berubah menjadi pesan error saat kompilasi, dan pemeriksaan itu selalu lebih murah daripada pelacakan saat berjalan.
Aturan arah juga mengubah cara membaca kode orang lain. Parameter hasil <-chan int pada sebuah fungsi langsung menyatakan bahwa fungsi itu konsumen murni, sedangkan parameter keluar chan<- int menyatakan produsen murni. Pada program yang memakai belasan goroutine, informasi semacam ini mempersempit pencarian sumber persoalan tanpa perlu membaca seluruh badan fungsi. Kompiler yang mengawasi peran adalah dokumentasi yang tidak pernah basi.
Konversi dua arah ke satu arah#
Konversi antar arah hanya berlaku satu arah. Nilai bertipe chan T dapat diberikan kepada variabel bertipe chan<- T maupun <-chan T tanpa penulisan apa pun, sebagaimana terjadi pada setiap pemanggilan hasilkan dan jumlahkan di atas, karena mempersempit kemampuan tidak pernah memperbesar risiko. Jalan baliknya ditolak kompiler, sebab nilai kirim-saja atau terima-saja tidak memuat informasi yang cukup untuk dipakai sebagai channel dua arah. Pemsempitan yang sama berlaku pada argumen fungsi, nilai kembalian, dan field struct, sehingga arah dapat diikat sebagai bagian dari tipe data yang lebih besar.
// tidak-dijalankan
func ujiKonversi() {
datar := make(chan int) // dua arah
var kirim chan<- int = datar // sah, dipersempit menjadi kirim-saja
var terima <-chan int = datar // sah, dipersempit menjadi terima-saja
// datar = kirim // ditolak kompiler, arah tidak dapat diperlebar kembali
_, _ = kirim, terima
}Dua penugasan pertama pada potongan di atas diterima kompiler, sedangkan baris terakhir yang mencoba mengembalikan arah memicu pesan error cannot use kirim (variable of type chan<- int) as chan int value in assignment. Pesan sepanjang itu lahir dari aturan yang sederhana, yaitu arah hanya dapat menyempit. Apabila sebuah fungsi benar-benar membutuhkan kirim dan terima pada channel yang sama, parameternya harus dinyatakan dua arah, dan keputusan itu pun tampak jelas pada tanda tangan fungsinya.
Pola produsen dan konsumen#
Pola yang paling sering memanfaatkan arah adalah pasangan produsen dan konsumen. Fungsi produsen (producer) menghasilkan nilai dan mengirimkannya, sedangkan fungsi konsumen (consumer) menerima dan mengolahnya. Keduanya saling tidak mengenal selain melalui tipe channel masing-masing. Pola ini terekspresi rapi karena kompiler memaksa setiap fungsi tetap pada perannya, sedangkan main merangkai keduanya lewat channel dua arah yang dibuatnya.
Program berikut menyerahkan daftar belanja kepada produsen, lalu konsumen memberi nomor pada setiap item yang diterimanya sebelum hasilnya dikumpulkan main.
package main
import "fmt"
func produsen(daftar []string, keluar chan<- string) {
for _, item := range daftar {
keluar <- item
}
close(keluar)
}
func konsumen(masuk <-chan string, hasil chan<- string) {
jumlah := 0
for item := range masuk {
jumlah++
hasil <- fmt.Sprintf("belanja ke-%d: %s", jumlah, item)
}
close(hasil)
}
func main() {
daftar := make(chan string)
hasil := make(chan string)
go produsen([]string{"beras", "minyak", "telur"}, daftar)
go konsumen(daftar, hasil)
for baris := range hasil {
fmt.Println(baris)
}
}belanja ke-1: beras
belanja ke-2: minyak
belanja ke-3: telurFungsi produsen menerima keluar chan<- string dan menutup channelnya sendiri setelah daftar habis, sedangkan konsumen menerima masuk <-chan string, memberi nomor pada setiap nilai yang diterima, lalu menutup channel hasilnya. Fungsi main membuat dua channel dua arah dan menyerahkannya kepada kedua fungsi, dan pada saat itulah konversi ke arah yang sempit terjadi secara diam-diam. Seluruh pencetakan dilakukan main lewat perulangan range atas channel hasil, sehingga keluaran tetap pasti meskipun pekerjaannya berjalan di goroutine terpisah.
Kesalahan umum#
- Mengonversi channel berarah kembali menjadi dua arah. Konversi hanya berlaku satu arah, dari dua arah ke berarah, sehingga fungsi yang butuh kirim dan terima harus menerima channel dua arah dari pemulutnya.
- Menutup channel terima-saja dengan berharap berjalan juga. Penutupan ditolak kompiler karena hanya pengirim yang tahu kapan datanya habis, dan aturan itu sudah terlihat dari tipenya.
- Menandai semua parameter sebagai channel dua arah demi fleksibilitas. Kebiasaan itu membuang jaminan kompiler dan mengembalikan beban pemeriksaan peran ke pembaca kode.
Ringkasan#
chan<- Tberarti kirim-saja dan<-chan Tberarti terima-saja, keduanya sudut pandang atas channel yang sama.- Konversi dari channel dua arah ke channel berarah terjadi otomatis, sedangkan jalan baliknya ditolak kompiler.
- Penutupan hanya sah pada tipe yang boleh mengirim, karena close memang hak pengirim.
- Arah pada tanda tangan fungsi menjadi dokumentasi yang diperiksa kompiler, dan pola produsen serta konsumen memanfaatkannya untuk merangkai tahap pemrosesan.
Referensi#
- Go Concurrency Patterns: Pipelines and cancellation — The Go Blog — Pola resmi yang memakai parameter channel berarah di setiap tahap.
- The Go Programming Language Specification — The Go Project — Spesifikasi resmi tipe channel dan arahnya.
- Effective Go — Go Documentation — Bagian resmi channel beserta kebiasaan pemakaiannya.