Channels
Channels (kanallar), Go’da goroutine’ler arasında veri alışverişini sağlamak için kullanılan yapılar olarak tanımlanabilir. Kanallar sayesinde, bir goroutine başka bir goroutine’e veri gönderebilir veya ondan veri alabilir. Kanallar, genellikle eşzamanlı işlemler arasında veri paylaşımı için kullanılır ve Go’nun eşzamanlı programlama modelinin temel yapı taşlarından biridir.
Özet olarak kanallar boru olarak düşünülebilir. Bir taraftan veri gönderilirken, diğer taraftan veri alınabilir.

Kanalların Kullanımı
Kanallar make fonksiyonu ile oluşturulur.
ch := make(chan int)Yukarıdaki örnekte içerisinden int tipinde veri aktarılabilen ch isminde bir kanal oluşturduk.
Unbuffered Channels (Arabelleksiz Kanallar)
Önceki örneğimizde tanımladığımız gibi arabelleksiz kanallar make fonksiyonu ile tanımlanırken sadece veri aktaracağı tip belirtilir.
1package main2
3import (4 "fmt"5 "time"6)7
8func main() {9 ch := make(chan string)10
11 go func() {12 time.Sleep(1 * time.Second)13 ch <- "hello" // 16. satırdan teslim alınana kadar bekleyecek14 }()15
16 a := <-ch // 13. satırdan gönderilene kadar bekleyecek17
18 fmt.Println(a) // hello19}Burada kanal kullanımı için bir örnek görmekteyiz. Kanallara veri gönderilirken veya okunurken <- operatörü kullanılır. Ok operatörü kanalı gösteriyorsa (ch <- "hello") kanala değer gönderilir. Eğer ok kanalın dışına doğru ise (a := <-ch) kanaldan veri okunur.
ch <- "selam"Yukarıdaki örnekte ch adlı kanala string tipinde değer gönderdik. Arabelleksiz (unbuffered) kanallarda veri gönderiminin tamamlanması için gönderilen verinin kanalın diğer ucundan teslim alınması gerekir. Aksi taktirde içerisinde çalışılan goroutine gönderme işlemi tarafından bloklanır.
a := <-chÖrneğimizde ch adlı kanaldan gelen veriyi a isminde değişkene atadık. Kanala gönderilen değer string tipinde olduğu için a değişkeninin tipi de string kabul edilir. Programımız ch kanalından değer gelene kadar bekleyecektir.
Kanallardan okunan değerleri bir değişkene atamadan, sadece veri gelmesini beklemek istiyorsak aşağıdaki gibi kullanabiliriz.
<-chBuffered Channels (Arabellekli Kanallar)
Arabellekli kanallar, aynı esnada belirlenen sayıda veri aktarabilen kanallardır. Tanımlanırken boyut belirtilir.
1package main2
3import (4 "fmt"5 "time"6)7
8func main() {9 ch := make(chan string, 4)10
11 go func() {12 ch <- "msg1"13 ch <- "msg2"14 ch <- "msg3"15 ch <- "msg4"16 fmt.Println("all msg sent")17 }()18
19 time.Sleep(2 * time.Second)20 a := <-ch21 b := <-ch22 c := <-ch23 d := <-ch24
25 fmt.Println(a, b, c, d) // msg1 msg2 msg3 msg426}Yukarıdaki örnekte aynı anda 4 adet string tipinde veri taşıyabilen bir kanal tanımlanmıştır. 12-15. satırlar arasında kanala değerler gönderilir. 5. kez değer gönderilmek istendiğinde programın bloklanmaması için kanaldan 1 değerin okunması gerekir. 20-23. satırlar arasında ise kanaldan değerler okunur. Dolayısıyla kanala yeni değerler gönderilmesi için yer açılmış olur.
Canlı Demo
Kanalların Kapatılması
Kanallar kullanılmadığında kapatılması gerekir. Bir kanalı kapatmak için close fonksiyonu kullanılır.
ch := make(string)...close(ch)veya içerisinde çalışılan fonksiyon tamamlandığında kapatılması için defer ile kullanabiliriz.
ch := make(string)defer close(ch)Kanalın kapatılmasının hali hazırda dinleme yapan yerler için etkilerine bakalım.
1ch := make(chan string, 3)2
3go func() {4 time.Sleep(2 * time.Second)5 ch <- "msg1"6 close(ch)7}()8
9a := <-ch // "msg1" gelecek10b := <-ch // "" (boş string) gelecek11
12fmt.Println(a, b) // "msg1" ""Yukarıdaki örneğe göre 5. satırda yazılan değer 9. satırda okunuyor. Devamında 6. satırda kanal kapatılıyor, bu yüzden 10. satırda okuma işlemi kanal kapatıldığından pas geçiliyor ve kanal string veri taşıdığından string’in varsayılan değeri olan "" (boş string) b değişkenine atanıyor.
Okuyucu tarafından boş string alındığında (örneğe göre 10. satır) boş string’in gelmesinin sebebi kanalın kapanması mı yoksa kanala gerçekten de boş string mi yollanmış bunu anlamak için comma ok kullanabiliriz.
1ch := make(chan string, 3)2
3go func() {4 time.Sleep(2 * time.Second)5 ch <- ""6 close(ch)7}()8
9a, ok1 := <-ch // "", true10b, ok2 := <-ch // "", false11
12fmt.Println(a, ok1, b, ok2) // "" true "" false5. satırda kanala bu sefer boş string değerini yolladık.
9. satırda kanaldan değer okunduğunda kanal açık olduğu için ok1 değeri true geldi. 10. satırda ise kanal kapalı olduğu için ok2’nin değeri false geldi. comma ok kullanarak kanaldan gelen varsayılan değerin kanal kapandığı için mi, yoksa boş string yollandığı için mi geldiğini anlayabildik.
Range ile Kanaldan Değer Okunması
range kullanılarak doğal olarak kanallar kapatılana kadar değer okuma (dinleme) işlemi yapılabilir.
1package main2
3import (4 "fmt"5)6
7func main() {8 ch := make(chan string)9
10 go func() {11 ch <- "msg1"12 ch <- "msg2"13 ch <- "msg3"14 close(ch)15 }()16
17 for v := range ch {18 fmt.Println(v)19 }20
21 fmt.Println("all msg received")22}ch isimli kanalımızı range ile kullanarak kanala yazılan (gönderilen) değerleri okuyabiliriz. 11, 12 ve 13. satırlarda değerlerimizi gönderdik. Devamında kanalı close kullanarak kapattık. Böylelikle kanalı dinleyen yerler artık kanala bir değer gönderilmeyeceğini bilip dinlemeyi pas geçecektir.
- satırda ise
rangeile beraberchkanalına gönderilen değerleri okuduk. Kanal kapatılana kadar okuma işlemine devam edildi.
Çıktımız aşağıdaki gibi olacaktır.
msg1msg2msg3all msg receivedKanala Yazma veya Okuma İşlemlerinde Deadlock
Deadlock, iki veya daha fazla goroutine’nin birbirlerini sonsuza dek beklemesinden meydana gelen bir durumdur. Deadlock go runtime’ı tarafından tespit edilebilir. Go runtime deadlock’ı tespit ettiğinde fatal error vererek programdan çıkış yapar.
Yukarıdaki örneği bir de deadlock hatası verecek şekilde yeniden kurgulayalım.
1package main2
3import (4 "fmt"5)6
7func main() {8 ch := make(chan string)9
10 go func() {11 ch <- "msg1"12 ch <- "msg2"13 ch <- "msg3"14 // close(ch)15 }()16
17 for v := range ch {18 fmt.Println(v)19 }20
21 fmt.Println("all msg received")22}Yukarıdaki örnekte bu sefer 14. satırda kanalı kapatmıyoruz. Bu yüzden kanalı dinleyen yerler sonsuza kadar dinlemede takılı kaldığı için go runtime deadlock tespit edip fatal error verecektir.
Çıktımızı görelim.
msg1msg2msg3fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:main.main() /path/to/example/project/main.go:17 +0xc4exit status 2Çıktıya göre main.go:17 satırında deadlock gerçekleştiği belirtiliyor.
Bu sefer yazma esnasında deadlock gerçekleşen bir örnek görelim.
1package main2
3import (4 "fmt"5)6
7func main() {8 ch := make(chan string)9
10 go func() {11 for v := range ch {12 fmt.Println(v)13 if v == "msg2" {14 break15 }16 }17 }()18
19 ch <- "msg1"20 ch <- "msg2"21 ch <- "msg3"22}Örneğimizde yazma ve okuma yerlerini değiştirdik. döngü içerisinde kanalı dinliyorken, eğer kanaldan msg2 değeri gelirse döngüyü sonlandırması için bir ekleme yaptık. Bu sayede msg3 değeri gelmeden önce kanalı dinlemeyi kestiğimiz için 21. satırda yeni değer yollanırken kanalı dinleyen bir yer bulunmayacak ve program bloklanacak.
Go runtime’ı deadlock’u tespit edip falat error verecek.
msg1msg2fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:main.main() /path/to/example/project/main.go:21 +0x9cexit status 2Çıktımızdaki hatada deadlock’un 21. satırda gerçekleştiğini belirtiyor. Arabelleksiz kanallarda programın bloklanmaması için kanala yazma işlemi yapılırken karşıdan okunması gerekir. Kanaldan okuma yapılana kadar yazma işlemi beklenir. Örneğimizde okuma yapacak kısım bulunmadığı için deadlock gerçekleşti.
Kanallarda Okuma veya Yazma Kısıtlamaları
Kanallar kullanılması için bir yere gönderilirken gönderildiği yer için okuma veya yazma kısıtlaması içerebilirler. Kısıtlama olmadan göndermek için,
func doSomething(ch chan string) { // ch <- "msg" // yazma // veya // a := <- ch // okuma}Sadece-Okuma Modu
Sadece-Yazma Modu
Okuma veya yazma kısıtlamaları fonksiyon return’lerinde de kullanılabilir.
func doSomething() chan string // Kısıtlama olmadan
// veya
func doSomething() <-chan string // Sadece-okuma modu
// veya
func doSomething() chan<- string // Sadece-yazma moduÖrneklerde belirtilen okuma veya yazma sınırlamasının faydalarına değinelim.
-
Kanalın sadece belirli bir amaç için kullanıldığını göstermek, kodu daha okunur ve anlaşılır hale getirir. Örneğin bir fonksiyonda parametrelerde veya return’de kanalın kısıtlama ile belirtilmesi niyeti daha açık hale getirir.
-
Okuma veya Yazmayı sınırlandırmak paralel programlamayı daha güvenilir bir hale getirebilir. Örneğin fonksiyon return’ünde dinleme amacı ile kullanıcak bir kanalı, sadece-okuma modu ile return edersek, bu kanala fonksiyon dışarısından yazılmasına engel olacaktır. Bu da kanala yetkisiz müdahaleler yapılmasını engeller.