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.