select
ud-218
Diyelim ki birden fazla kanalı dinlemek istiyoruz ve hangisinde veri varsa
onu işlemek istiyoruz. İşte burada select..case idiom’u devreye giriyor.
Aşağıdaki koda bakalım:
package main
import "fmt"
func main() {
cift := make(chan int)
tek := make(chan int)
quit := make(chan int)
go tx(cift, tek, quit)
rx(cift, tek, quit)
fmt.Println("Çıkış yapıyoruz.")
}
// 3 parametre de aynı türden
func tx(cift, tek, quit chan<- int) {
for i := 0; i < 10; i++ {
if i%2 == 0 {
cift <- i
} else {
tek <- i
}
}
close(cift)
close(tek)
quit <- 0
}
// 3 parametre de aynı türden
func rx(cift, tek, quit <-chan int) {
for {
select {
case v := <-cift:
fmt.Println("Çift:", v)
case v := <-tek:
fmt.Println("Tek:", v)
case <-quit:
return
}
}
}
Burada 3 adet kanal yaratıyor, tek, cift ve quit olarak. quit sadece
çalışmanın bitmiş olduğunu belirtmek için kullanılıyor. Alan tarafta
select..casei görüyoruz. Burada hangi kanalda veri varsa o case çalışıyor.
Birden fazla kanalda veri varsa hangisinin kullanılacağının ise garantisi yok.
Eğer sıra ile bakıyor olsaydı kanallar arasında aslında bir priority yani
öncelik tanımlamış olurduk. Bu select..case in başında olan kanalların her
zaman işlem öncelliği almasına ve diğerlerinin starvation çekmesine sebep
olacaktı. Burada ise bir pseudo-random bir yapı var. Elbette bir algoritması
var ama şu an konumuz değil. Burada önemli olan nokta şu:
Programcı select..case içerisinde bir öncelik varsayımı yapmamalı, kodu
buna göre yazmamalıdır.
Yukarıda belki ilginç gelebilecek bir kısım tx() ve rx() parametrelerinin
i, j, k <T> şeklinde tanımlanmasıdır. Burada tüm parametreler aynı türden
olmaktadır.
case v := <-cift gibi kullanımda kanalda okunan veri v değişkenine
atanırken, case <-quit kullanımında kanaldan veri okunmakta ama discard
edilmektedir.
Şimdi kodu çalıştıralım ve çıktısına bakalım:
Çift: 0
Tek: 1
Çift: 2
Tek: 3
Çift: 4
Tek: 5
Çift: 6
Tek: 7
Çift: 8
Tek: 9
Çift: 0
Tek: 0
Çift: 0
Çıkış yapıyoruz.
Şimdi 0-9 arası yazdırma kısmı beklediğimiz gibi ama sonrasında quit
kanalından veri alarak return edene kadar Çift ve Tek karışık 0 basıyoruz,
neden? Bir önceki notlarda, kapanmış kanalların o kanalın
taşıdığı türün sıfır değerini, int için 0, döndüğünü söylemiştik. İşte biz
cift ve tek kanalı kapatsak da artık bu kanallar sürekli ready konumdadır
ve 0 dönmektedir. İşte burada select..case “şansa” quit kanalını okuyana
kadar cift ve tek kanallardan sürekli 0 okumaktayız. Burada hiç bloke
olmamaktadır. Üstteki kodun bazı çalıştırmalarda doğru çalıştığını
görebilirsiniz, diğer durumlarda da yanlış basılan 0 sayısı değişmektedir.
Burası tahmin edilebilir değildir. Bunun nasıl çözülebileceğini ileride
göreceğiz.
Eğer close(cift) ve close(tek) ile kanalları kapatmazsak bu kodda doğru
çalışma görebiliriz. Çünkü o kanallardaki veriler bittiği zaman kanalı da
kapatmadığımız için kanal okumaları bloke olmaktadır ve çıktı beklediğimiz gibi
olmaktadır.