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.