Mengirim dan menerima
Channel adalah pipa bertipe di antara goroutine. ch <- v mengirim, <-ch menerima. Buat channel dengan make:
Zero value dari tipe channel adalah nil, jadi var ch chan string tanpa make memberimu channel yang tertahan selamanya. Selalu buat channel dengan make.
Channel unbuffered menyinkronkan
make(chan T) membuat channel unbuffered. Pengiriman tertahan sampai ada penerima yang mengambil nilainya, dan penerimaan tertahan sampai ada pengirim yang memberikannya. Kedua goroutine bertemu di titik itu, sehingga channel unbuffered sama pentingnya sebagai alat sinkronisasi maupun sebagai pipa data. Saat <-done di bawah ini selesai, kamu tahu worker sudah menyelesaikan semua yang ada sebelum pengirimannya:
Membaca result di main aman di sini tanpa mutex. Model memori Go menjamin bahwa semua yang dilakukan worker sebelum pengiriman terlihat oleh main setelah penerimaan yang cocok. chan struct{} adalah tipe yang idiomatis untuk sinyal murni, karena struct{} tidak memakai memori.
Channel buffered
make(chan T, n) memberi channel ruang untuk n nilai. Pengiriman berhasil tanpa penerima sampai buffer penuh; penerimaan berhasil sampai buffer kosong. Nilai keluar dengan urutan yang sama seperti saat masuk.
Buffer memisahkan pengirim dan penerima sehingga lonjakan singkat tidak membuat pengirim macet. Buffer tidak memperbaiki producer yang selalu lebih cepat dari consumer-nya; buffer hanya menunda saat pengirim tertahan. Pilih ukuran buffer karena alasan yang jelas (jumlah pengirim, ukuran batch yang sudah diketahui), bukan untuk menghilangkan deadlock.
len(ch) hanyalah potret sesaat. Saat kamu bertindak berdasarkan nilainya, goroutine lain mungkin sudah mengubahnya, jadi jangan memakainya untuk menentukan apakah pengiriman akan tertahan. Untuk itu, pakai select dengan case default.
Close dan range
close(ch) memberi tahu penerima bahwa tidak akan ada nilai lagi yang dikirim. Setelah close:
- nilai yang sudah ada di buffer tetap dikirimkan,
- lalu setiap penerimaan langsung mengembalikan zero value,
v, ok := <-chmelaporkanok == false,for v := range chberakhir.
Penerimaan pertama setelah close masih mendapat "last" dengan ok == true. Penerimaan kedua mendapat zero value "" dan false.
Aturan yang menyebabkan panic:
- mengirim ke channel yang sudah ditutup akan panic dengan
send on closed channel, - menutup channel yang sudah ditutup akan panic,
- menutup channel
nilakan panic.
Jadi hanya pihak pengirim yang menutup, dan hanya sekali. Dengan beberapa pengirim, tidak ada satu pengirim pun yang tahu kapan pengirim lain selesai; buat goroutine terpisah yang menunggu semua pengirim (dengan sync.WaitGroup) dan menutup channel setelah Wait selesai. Menutup channel hanya perlu kalau ada penerima yang menunggu akhirnya. Channel yang tidak ditutup dan tidak lagi dirujuk siapa pun dibersihkan oleh garbage collector seperti nilai lainnya.
Tipe arah
Sebuah fungsi bisa menyatakan bahwa ia hanya mengirim atau hanya menerima pada sebuah channel. Compiler lalu menolak operasi yang lain.
| Tipe | Arti | Diizinkan |
|---|---|---|
chan T | dua arah | kirim, terima, close |
chan<- T | hanya kirim | kirim, close |
<-chan T | hanya terima | terima |
chan T otomatis dikonversi ke salah satu tipe terbatas itu saat kamu mengirimnya ke fungsi. Karena itulah produce di atas bisa mengembalikan <-chan int: pemanggilnya bisa melakukan range atasnya, tetapi tidak bisa mengirim ke dalamnya atau menutupnya. Pakai tipe arah di setiap parameter fungsi yang cocok. Tipe arah mendokumentasikan kepemilikan dan mengubah penyalahgunaan menjadi error kompilasi.
Deadlock
Jika setiap goroutine tertahan dan tidak ada yang bisa membangunkan satu pun di antaranya, runtime menghentikan program:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/main.go:7 +0x38
exit status 2
Dump goroutine memberi tahu operasi mana yang macet (di sini chan send, atau chan receive, sync.WaitGroup.Wait, select). Penyebab yang biasa:
- pengiriman ke channel unbuffered tanpa ada penerima yang berjalan,
rangeatas channel yang tidak pernah ditutup,- counter
WaitGroupyang tidak pernah mencapai nol, - dua goroutine yang saling menunggu.
Runtime hanya mendeteksi kasus ketika semua goroutine macet. Di server yang masih punya goroutine lain yang hidup (listener HTTP, ticker), bug yang sama tidak membuat apa pun crash; goroutine yang tertahan hanya bocor.
Channel nil
Pengiriman dan penerimaan pada channel nil tertahan selamanya. Kedengarannya tidak berguna, tetapi ini trik standar di dalam select: mengisi variabel channel dengan nil menonaktifkan case-nya. Contoh ini menggabungkan dua channel dan berhenti mendengarkan masing-masing begitu channel itu ditutup:
Tanpa assignment nil, channel yang sudah ditutup selalu siap, dan perulangan akan terus berputar pada zero value.
Pipeline
Channel bisa disusun menjadi pipeline: setiap tahap adalah goroutine yang menerima dari satu channel dan mengirim ke channel berikutnya, lalu menutup output-nya saat input-nya selesai.
Setiap tahap berjalan secara konkuren, dan urutan output-nya pasti (1, 16, 81) karena setiap tahap adalah satu goroutine yang menjaga urutan. Penutupan berantai: generate menutup channel, yang mengakhiri range di square, yang lalu menutup output-nya, dan seterusnya sampai ke main.
Titik lemah pipeline ini: kalau main berhenti membaca lebih awal, tahap-tahapnya akan tertahan selamanya saat mengirim. Pipeline sungguhan menerima context.Context atau channel done dan melakukan select atasnya di samping setiap pengiriman.
Channel atau mutex
Channel dipakai untuk menyerahkan kepemilikan data dan memberi sinyal tentang suatu kejadian. sync.Mutex lebih sederhana untuk melindungi state bersama yang dibaca dan diubah langsung oleh banyak goroutine, seperti cache atau counter. Struct yang berisi mutex sering kali lebih jelas daripada goroutine yang memiliki state dan melayani permintaan lewat channel. Pakai yang membuat kodenya lebih pendek dan kepemilikannya jelas.
Referensi singkat
| Operasi | channel nil | channel terbuka | channel tertutup |
|---|---|---|---|
ch <- v | tertahan selamanya | tertahan sampai diterima atau buffer punya ruang | panic |
<-ch | tertahan selamanya | tertahan sampai ada nilai | nilai di buffer, lalu zero value |
v, ok := <-ch | tertahan selamanya | ok bernilai true | ok bernilai false setelah kosong |
close(ch) | panic | menutup | panic |
len(ch), cap(ch) | 0, 0 | jumlah nilai di buffer, ukuran buffer | nilai yang tersisa, ukuran buffer |
Pertanyaan yang sering diajukan
Apa bedanya channel buffered dan unbuffered di Go?
Channel unbuffered (make(chan int)) tidak punya tempat penyimpanan: pengiriman tertahan sampai goroutine lain menerimanya, jadi setiap pengiriman juga merupakan serah terima dan titik sinkronisasi. Channel buffered (make(chan int, 3)) menampung hingga 3 nilai; pengiriman hanya tertahan saat buffer penuh, dan penerimaan hanya tertahan saat buffer kosong.
Apa yang terjadi kalau membaca dari channel yang sudah ditutup di Go?
Penerimaan dari channel yang sudah ditutup tidak pernah tertahan. Pertama, nilai yang masih ada di buffer dikeluarkan, lalu seterusnya yang dikembalikan adalah zero value dari tipe elemennya. Pakai v, ok := <-ch untuk membedakannya: ok bernilai false begitu channel sudah ditutup dan kosong. Perulangan for v := range ch berhenti di titik itu.
Siapa yang seharusnya menutup channel di Go?
Pengirimnya, dan hanya kalau penerima perlu tahu bahwa tidak ada nilai lagi yang akan datang (misalnya untuk mengakhiri perulangan range). Mengirim ke channel yang sudah ditutup akan panic, begitu juga menutup channel dua kali, jadi penerima yang menutup channel akan berlomba dengan pengirim. Kamu tidak perlu menutup channel untuk membebaskannya; garbage collector tetap membersihkan channel yang sudah tidak bisa dijangkau.
Apa arti "fatal error: all goroutines are asleep"?
Setiap goroutine di program sedang tertahan pada operasi channel atau lock yang tidak akan pernah bisa selesai, jadi runtime menghentikan program. Penyebab paling umum adalah mengirim ke channel unbuffered di main tanpa ada goroutine lain yang menerima, atau melakukan range atas channel yang tidak pernah ditutup.