range ve close()

ud-217

Şimdiye kadar channel örneklerinde bir şekilde veri çeken kısım kaç adet veri çekeceğini biliyordu. Ama birçok uygulamada veriyi kanaldan okuyan kısım kaç adet alma yapması gerektiğini veya ne zaman artık yeni gönderim olmayacağını bilemez. Şimdi de bu duruma bakalım. Önce aşağıdaki kodu inceleyelim.

package main

import "fmt"

func main() {
	ch := make(chan int)

	go tx(ch)
	rx(ch)
	// ch <- 42 // panic: send on closed channel

	fmt.Println("Çıkış yapıyoruz.")
}

func tx(ch chan<- int) {
	for i := 0; i < 100; i++ {
		ch <- i
	}
	close(ch) // Olmazsa, fatal error: all goroutines are asleep - deadlock!
}

func rx(ch <-chan int) {
	for v := range ch {
		fmt.Println(v)
	}
}

Bir önceki notlardaki uygulamadan biraz faklı bir uygulama yapıyoruz. Gönderen kısım bir loop içerisinde kanala sürekli veri gönderiyor. Burada alan tarafta range ch idiom’umu görüyoruz. Buradaki for loop kanalda okunabilir bir veri olduğu zaman ilerliyor ve ekrana kanaldan çekilen veriyi görüyoruz. Eğer kanalda okunabilir bir veri yoksa range ch bloke olup bekliyor. Tahmin edeceğiniz gibi sırası ile ekrana tüm sayıları basmış oluyoruz çıkış olarak.

Burada tx() fonksiyonu içerisinde bir de yeni gördüğümüz close(ch) ifadesi mevcut. Bu fonksiyon ile kanalı kapatmış oluyoruz. Böylece rx() fonksiyonu gibi for..range ile bekleyenler kanalın kapandığını anlıyor. Bu durumda rx() içerisindeki for loop sonlanıyor.

Yukarıdaki örnekte close(ch) yazmazsak ne olur? Bu örnekte tx() sonlandıktan sonra kanalı kapatacak kimse kalmadığı için ve rx() sonsuz döngüde bekleyeceği için Go runtime bize deadlock hatası verecektir.

Son olarak main() içerisindeki yorumla kapatılmış ch <- 42 satırına bakalım. Bu satıra geldiğimiz zaman biliyoruz ki rx() fonksiyonunun bitmiş olması lazım. O da rx() içerisindeki for..range in bitmesi ile mümkün. Bu da anca ch nin kapatılması ile mümkün. Yani ch <- 42 aslında kapatılmış bir kanala yazma yapıyor. Dikkat ederseniz tx() içerisindeki ch ile main() içerisindeki ch farklı gibi duruyor ilk başta, scope kurallarından dolayı. Ama önceden de bahsetmiştik, kanallar aslında birer referans (bir nevi pointer) gibi, yani kanal tutan değişkenler aslında tek ve aynı kanalı gösteriyor. O yüzden, tx() içerisinde close() ettiğimiz zaman aslında o tek kanal kapanıyor. Eğer kapalı kanala yazarsak Go runtime panic vermektedir. Go dili kurallarına göre kapalı kanala yazmak böyle sonuçlanır. Kapalı kanalda okuma yapmak ise böyle bir durum yaratmaz. Bu durumda v := -<ch ile okuma yaparsak o tipin sıfır değeri, (0, false, "") elde edilir. Bunu yukarıdaki kodu

fmt.Println(<-ch) //Ekrana 0 basacaktır, int'in "sıfır değeri" 0'dır.
fmt.Println("Çıkış yapıyoruz.")

şeklinde değiştirerek test edebiliriz.


Önerilen, kanalı yazan tarafın kapatmasıdır. Çünkü alan kişi kanalı kapatırsa ve bu erken bir kapatma ise bu sefer gönderen taraf yazma yapmaya çalışınca panic durumu oluşur. Bu da programın çakılması demektir.

close() aslında bir broadcast sinyali gibi de davranır. Yani o kanalda bloke olmuş 10 tane goroutine varsa, close() ile hepsini uyandırmış oluruz. Herkese işimizin bittiğini söyleme yöntemi olarak da kullanılabilir.