Pernyataan select menunggu peristiwa pada beberapa channel sekaligus, lalu menjalankan cabang pertama yang siap. Topik ini membahas struktur select dengan beberapa cabang when, cabang else untuk keadaan tidak ada cabang yang siap, cabang timeout untuk membatasi lama penungguan, serta kesalahan yang lazim terjadi ketika select baru pertama kali digunakan. Setiap perilaku diilustrasikan dengan program yang dijalankan dan keluaran aslinya dicantumkan apa adanya.
Menunggu beberapa channel sekaligus#
Tanpa select, program yang menunggu beberapa channel memerlukan satu fiber penerima untuk setiap channel, sehingga koordinasi hasilnya tersebar di beberapa tempat. Pernyataan select menghimpun seluruh penungguan itu dalam satu blok. Strukturnya terdiri atas kata select, satu cabang when untuk setiap channel yang ikut ditunggu, dan penutup end. Nilai yang diterima dapat ditampung dengan penugasan pada kondisi cabang, misalnya pesan = channel.receive, sehingga isi blok cabang dapat menggunakan nilai tersebut.
Program berikut menunggu dua channel sensor bertipe Channel(Int32). Program menjalankan select tiga kali pada keadaan yang berbeda, yaitu ketika hanya channel pertama yang berisi nilai, ketika hanya channel kedua yang berisi nilai, dan ketika keduanya berisi nilai pada saat yang sama.
suhu = Channel(Int32).new(1)
kelembapan = Channel(Int32).new(1)
suhu.send(28)
select
when nilai = suhu.receive
puts "sensor suhu: #{nilai}"
when nilai = kelembapan.receive
puts "sensor kelembapan: #{nilai}"
end
kelembapan.send(70)
select
when nilai = suhu.receive
puts "sensor suhu: #{nilai}"
when nilai = kelembapan.receive
puts "sensor kelembapan: #{nilai}"
end
suhu.send(29)
kelembapan.send(71)
select
when nilai = suhu.receive
puts "sensor suhu: #{nilai}"
when nilai = kelembapan.receive
puts "sensor kelembapan: #{nilai}"
end# menjalankan program select dengan dua channel
crystal run select-dua-channel.crsensor suhu: 28
sensor kelembapan: 70
sensor suhu: 29Pada ronde pertama dan kedua, hanya satu channel yang berisi nilai ketika select dieksekusi, sehingga cabang channel itu yang dijalankan dan cabang lainnya dilewati. Pada ronde ketiga, kedua channel sama-sama siap, dan Crystal memeriksa cabang sesuai urutan penulisannya, sehingga cabang suhu.receive yang terpilih. select mengeksekusi satu cabang saja per pernyataan, sedangkan nilai yang belum diambil tetap menunggu di channelnya masing-masing. Dua ronde pertama sekaligus membuktikan bahwa cabang yang menunggu channel kosong tidak menghalangi cabang lain yang sudah siap.
Cabang else untuk pemeriksaan tanpa menunggu#
Cabang else dijalankan segera apabila tidak ada satu pun cabang when yang siap pada saat select dieksekusi. Pernyataan select yang memiliki cabang else karenanya tidak pernah memblokir, sekalipun seluruh channelnya dalam keadaan kosong. Pola ini digunakan untuk mengosongkan antrean selama masih ada isinya, lalu melakukan langkah lain ketika antrean telah kosong.
Program berikut mengisi channel berbuffer dengan tiga nilai, kemudian menjalankan select sebanyak empat kali di dalam perulangan. Tiga iterasi pertama menemukan nilai, sedangkan iterasi keempat menemukan antrean yang sudah kosong.
antrean = Channel(String).new(3)
antrean.send("berkas a")
antrean.send("berkas b")
antrean.send("berkas c")
4.times do
select
when tugas = antrean.receive
puts "memproses #{tugas}"
else
puts "antrean kosong"
end
end# menjalankan program select dengan cabang else
crystal run select-perulangan.crmemproses berkas a
memproses berkas b
memproses berkas c
antrean kosongIterasi keempat adalah bukti perilaku else sekaligus alasan keberadaannya. Tanpa else, pemanggilan antrean.receive yang keempat memblokir program selamanya karena tidak akan ada nilai baru. Dengan else, keadaan kosong terdeteksi seketika dan program melanjutkan langkah berikutnya. Kombinasi select dan else di dalam perulangan juga menjadi cara sederhana untuk memeriksa ketersediaan data secara berkala tanpa menyerahkan kendali selamanya kepada satu channel.
Batas waktu dengan timeout#
Metode timeout(durasi) digunakan sebagai cabang select yang menjadi siap setelah durasi yang diberikan berlalu, tanpa memerlukan require tambahan. Cabang ini menjawab kebutuhan yang tidak dapat dijawab else, yaitu memberi kesempatan kepada channel untuk menerima data, tetapi tetap membatasi lama penungguannya. Program berikut memperlihatkan kedua keadaannya dalam satu berkas, yaitu channel yang diam total dan channel yang menerima pesan sebelum batas waktu tercapai.
lambat = Channel(String).new
select
when pesan = lambat.receive
puts "pesan diterima: #{pesan}"
when timeout(50.milliseconds)
puts "waktu tunggu habis, tidak ada pesan"
end
pengirim = Channel(String).new
spawn do
sleep 50.milliseconds
pengirim.send("datang tepat waktu")
end
select
when pesan = pengirim.receive
puts "pesan diterima: #{pesan}"
when timeout(5.seconds)
puts "waktu tunggu habis, tidak ada pesan"
end# menjalankan program select dengan batas waktu
crystal run select-timeout.crwaktu tunggu habis, tidak ada pesan
pesan diterima: datang tepat waktuChannel lambat tidak pernah menerima apa pun, sehingga setelah 50 milidetik cabang timeout menjadi siap dan dijalankan. Pada ronde kedua, fiber pengirim tidur selama 50 milidetik sebelum mengirim, sedangkan batas waktunya 5 detik, sehingga cabang receive menang jauh sebelum timeout sempat siap. Perbandingan kedua ronde menunjukkan bahwa pemenang select ditentukan oleh peristiwa mana yang terjadi lebih dulu, bukan oleh urutan penulisan cabang ketika hanya satu cabang yang siap.
Kesalahan umum#
Tiga kesalahan berikut lazim muncul ketika select baru pertama kali digunakan.
- Mengira
selectmenunggu semua channel sekaligus tuntas.selecthanya menunggu sampai satu cabang siap, lalu menjalankan cabang itu dan selesai. Apabila seluruh nilai harus dikumpulkan, terapkanselectdi dalam perulangan dan hitung jumlah penerimaan, atau gunakan satu fiber penerima per channel. - Menulis
selecttanpaelsemaupuntimeoutuntuk data yang belum pasti datang. Pernyataan semacam itu menunggu tanpa batas apabila pengirimnya gagal berjalan. Bungkus penungguan dengan cabangtimeoutagar program dapat melapor dan melanjutkan langkah lain. - Menganggap
selectmelindungi dari channel yang sudah ditutup. Cabangreceivepada channel yang telah ditutup tetap memicu errorChannel::ClosedError, baik di dalam maupun di luarselect. Tangani error tersebut dengan blokrescue, atau gunakanreceive?yang mengembalikannil.
Ringkasan#
- Pernyataan
selectmenunggu beberapa channel sekaligus dan menjalankan tepat satu cabangwhen, yaitu cabang yang channelnya siap lebih dulu. - Apabila lebih dari satu cabang siap pada saat yang sama, Crystal memeriksa cabang sesuai urutan penulisannya dan memilih cabang siap yang pertama.
- Cabang
elsedijalankan segera ketika tidak ada cabang yang siap, sehinggaselectyang memilikinya tidak pernah memblokir. - Cabang
timeout(durasi)menjadi siap setelah durasi berlalu dan menjadi cara baku untuk membatasi lama penungguan.
Referensi#
- Concurrency guide — Crystal
— panduan resmi konkurensi Crystal yang membahas fiber dan channel sebagai dasar
select. - Channel — Crystal API
— dokumentasi kelas Channel, termasuk metode
receive,send, danreceive?yang digunakan pada cabangselect.