Topik ini membahas pernyataan select, yaitu sarana bagi satu goroutine untuk menunggu beberapa operasi channel sekaligus dan melanjutkan eksekusi pada operasi yang siap lebih dahulu. Setelah membacanya, laporan dari beberapa channel dapat dikumpulkan lewat satu perulangan, waktu tunggu terhadap channel yang diam dapat dibatasi dengan time.After, dan pemeriksaan channel tanpa menunggu dapat ditulis dengan default. Ketiganya dibahas dengan program utuh beserta keluaran aslinya.
Menunggu beberapa channel sekaligus#
Pernyataan select menampung beberapa case, dan setiap case berupa satu operasi pengiriman atau penerimaan pada channel. Pernyataan ini menahan goroutine sampai salah satu case siap, lalu menjalankan isi case tersebut saja. Apabila lebih dari satu case siap pada saat yang sama, select memilih salah satunya secara acak, sehingga urutan penulisannya tidak boleh diandalkan. Pola menunggu banyak channel dengan satu pernyataan inilah yang disebut multiplexing, yaitu menyatukan aliran data dari beberapa sumber ke satu titik penanganan.
Program berikut menggabungkan laporan dua sensor suhu ke dalam satu perulangan. Perulangan for menampung satu select yang melayani dua channel sekaligus, sedangkan channel yang sudah ditutup dinonaktifkan dengan mengubah variabelnya menjadi nil.
package main
import (
"fmt"
"time"
)
func sensor(nama string, derajat []int, keluar chan<- string) {
defer close(keluar)
for _, d := range derajat {
time.Sleep(10 * time.Millisecond)
keluar <- fmt.Sprintf("%s: %d derajat Celsius", nama, d)
}
}
func main() {
dapur := make(chan string)
gudang := make(chan string)
go sensor("dapur", []int{24, 25}, dapur)
go sensor("gudang", []int{19, 20}, gudang)
var catatanDapur, catatanGudang []string
for dapur != nil || gudang != nil {
select {
case bacaan, buka := <-dapur:
if !buka {
dapur = nil
continue
}
catatanDapur = append(catatanDapur, bacaan)
case bacaan, buka := <-gudang:
if !buka {
gudang = nil
continue
}
catatanGudang = append(catatanGudang, bacaan)
}
}
fmt.Println("bacaan diterima:", len(catatanDapur)+len(catatanGudang))
for _, c := range catatanDapur {
fmt.Println(c)
}
for _, c := range catatanGudang {
fmt.Println(c)
}
}bacaan diterima: 4
dapur: 24 derajat Celsius
dapur: 25 derajat Celsius
gudang: 19 derajat Celsius
gudang: 20 derajat CelsiusSetiap case memakai bentuk bacaan, buka := <-channel, sehingga penutupan channel dikenali dari nilai false pada variabel kedua. Penerimaan dari channel yang sudah ditutup selalu siap dan selalu mengembalikan zero value, sehingga perulangan akan berputar terus membaca nilai kosong apabila case itu tidak dinonaktifkan. Penugasan dapur = nil mengatasi hal tersebut, karena operasi pada channel nil menunggu selamanya dan case-nya praktis berhenti dipertimbangkan. Setelah kedua channel bernilai nil, kondisi perulangan bernilai false dan main melanjutkan ke pencetakan.
Urutan kedatangan bacaan antara dua sensor tidak dijamin, karena select memilih secara acak ketika kedua channel sama-sama siap. Yang dijamin hanyalah urutan di dalam satu channel, sebab channel selalu menjaga urutan pengirimannya. Oleh karena itu, catatan ditampung per sensor dan dicetak setelah seluruh penerimaan selesai. Pencetakan langsung di dalam select akan menyandingkan baris dari dua sensor dalam urutan yang berubah-ubah pada setiap dijalankan.
Batas waktu dengan time.After#
Penerimaan pada channel yang tidak pernah dikirimi apa pun menunggu selamanya, dan select menyediakan jalan keluarnya dalam bentuk pewaktu. Fungsi time.After(durasi) mengembalikan channel terima-saja yang mengirim satu nilai time.Time setelah durasi tersebut berlalu, sehingga case <-time.After(...) berperan sebagai batas waktu. Apabila data tiba lebih dahulu, case datanya yang dijalankan, sedangkan apabila pewaktunya berlalu lebih dahulu, penungguan berakhir pada cabang kegagalan.
Program berikut menguji dua sensor dalam satu jalankan. Sensor dapur mengirim laporan sepuluh milidetik setelah program dimulai, sedangkan sensor gudang tidak melaporkan apa pun. Batas waktunya dipasang satu detik, jauh di atas jeda laporan, sehingga cabang yang terpilih sama pada setiap eksekusi.
package main
import (
"fmt"
"time"
)
func main() {
dapur := make(chan int)
gudang := make(chan int)
go func() {
time.Sleep(10 * time.Millisecond)
dapur <- 24
}()
select {
case d := <-dapur:
fmt.Println("dapur, bacaan diterima:", d)
case <-time.After(time.Second):
fmt.Println("dapur, waktu tunggu habis")
}
select {
case d := <-gudang:
fmt.Println("gudang, bacaan diterima:", d)
case <-time.After(time.Second):
fmt.Println("gudang, waktu tunggu habis")
}
}dapur, bacaan diterima: 24
gudang, waktu tunggu habisLaporan sensor dapur tiba kira-kira sepuluh milidetik, jauh sebelum pewaktu satu detik berlalu, sehingga case penerimaannya yang dipilih. Sensor gudang tidak pernah mengirim, sehingga satu-satunya case yang siap adalah pewaktnya dan program mencetak pesan waktu tunggu habis. Kedua durasi sengaja dibuat sangat berjarak karena selisih penjadwalan yang wajar tidak boleh membalik keputusan select. Durasi yang berdekatan, misalnya sinyal lima puluh milidetik dan batas waktu seratus milidetik, membuat keputusan program bergantung pada kecepatan mesinnya.
Pemeriksaan tanpa menunggu dengan default#
Case default mengubah select menjadi pemeriksaan yang tidak pernah menunggu. Apabila tidak ada satu pun case channel yang siap pada saat pernyataan dijalankan, case default langsung dipilih. Bentuk ini cocok untuk memeriksa ketersediaan data sambil melanjutkan pekerjaan lain, misalnya memeriksa laporan yang belum datang sambil mengisi catatan.
Program berikut memeriksa sebuah channel pesan yang tidak pernah dikirimi apa pun.
package main
import "fmt"
func main() {
pesan := make(chan string)
select {
case p := <-pesan:
fmt.Println("pesan diterima:", p)
default:
fmt.Println("belum ada pesan, pekerjaan lain dilanjutkan")
}
}belum ada pesan, pekerjaan lain dilanjutkanTanpa default, select pada program di atas menunggu selamanya, dan karena tidak ada goroutine lain yang hidup, runtime menghentikannya dengan pesan deadlock. Dengan default, pernyataan selesai seketika dan program berlanjut. Perlu diperhatikan bahwa default hanya menilai keadaan pada satu titik waktu, sehingga data yang datang sesaat kemudian tidak terlihat oleh pemeriksaan yang sudah selesai.
Kesalahan umum#
- Membuat
time.Aftersekali di luar perulangan lalu memakainya sebagai batas waktu setiap putaran. Pewaktunya hanya mengirim satu nilai, sehingga pada putaran berikutnya penerimaan dari channel itu menunggu tanpa batas waktu dan case-nya tidak pernah siap lagi. Panggiltime.Afterdi dalam badan perulangan agar setiap putaran memperoleh pewaktu baru. - Mengandalkan urutan penulisan case saat beberapa channel siap bersamaan. Select memilih secara acak di antara case yang siap, sehingga hasilnya dapat berbeda pada setiap dijalankan. Tampung hasil per sumber dan cetak setelah seluruh penerimaan selesai.
- Menerima dari channel yang sudah ditutup di dalam perulangan select tanpa menonaktifkannya. Penerimaan dari channel tertutup selalu siap dan selalu mengembalikan zero value, sehingga perulangan berputar tanpa henti. Ubah variabel channel menjadi
nilsetelah penutupan terdeteksi, atau akhiri perulangan pada saat itu.
Ringkasan#
- Select menunggu beberapa operasi channel sekaligus dan menjalankan satu case yang siap, dipilih secara acak bila lebih dari satu siap.
- Channel yang ditutup dinonaktifkan di dalam select dengan mengubah variabelnya menjadi
nilagar case-nya tidak terpilih lagi. time.Aftermenyediakan batas waktu berupa channel yang mengirim satu nilai setelah durasi tertentu berlalu.- Case
defaultmembuat select selesai seketika bila tidak ada satu pun case channel yang siap.
Referensi#
- A Tour of Go: Concurrency — Go Documentation — Latihan resmi select, termasuk bentuk default dan pewaktunya.
- package time — Go
— Dokumentasi resmi
time.Afterbeserta semantik pewaktunya. - The Go Programming Language Specification — The Go Project — Spesifikasi resmi pernyataan select.