Kanalları iletişim mekanizması olarak kullanma
Go'daki kanal, goroutinler arasındaki bir iletişim mekanizmasıdır. Go'nun eşzamanlılık yaklaşımının şu olduğunu unutmayın: "Bellek paylaşarak iletişim kurmayın; bunun yerine iletişim kurarak bellek paylaşın." Bir goroutine'den diğerine değer göndermeniz gerektiğinde, kanalları kullanırsınız. Şimdi bunların nasıl çalıştığını ve eşzamanlı Go programları yazmak için bunları kullanmaya nasıl başlayabileceğinizi görelim.
Kanal söz dizimi
Kanal, veri gönderip alan bir iletişim mekanizması olduğundan, aynı zamanda bir türü de vardır. Bu, yalnızca kanalın desteklediği türde veriler gönderebileceğiniz anlamına gelir. Anahtar sözcüğünü chan
bir kanalın veri türü olarak kullanırsınız, ancak bir tür gibi int
kanaldan geçecek veri türünü de belirtmeniz gerekir.
Bir kanalı her bildirdiğinizde veya bir işlevde parametre olarak bir kanal belirtmek istediğinizde, gibi chan int
kullanmanız chan <type>
gerekir. Kanal oluşturmak için yerleşik make()
işlevini kullanırsınız:
ch := make(chan int)
Kanal iki işlem yapabilir: veri gönderme ve veri alma . Kanalın sahip olduğu işlem türünü belirtmek için kanal işlecini <-
kullanmanız gerekir. Ayrıca, kanallarda veri gönderme ve veri alma işlemleri engelliyor. Nedenini birazdan göreceksiniz.
Kanalın yalnızca veri gönderdiğini söylemek istediğinizde, kanalın ardından <-
işlecini kullanın. Kanalın veri almasını istediğinizde, aşağıdaki örneklerde olduğu gibi kanaldan önce işlecini kullanın <-
:
ch <- x // sends (or writes ) x through channel ch
x = <-ch // x receives (or reads) data sent to the channel ch
<-ch // receives data, but the result is discarded
Kanalda kullanabileceğiniz bir diğer işlem de kanalı kapatmaktır. Bir kanalı kapatmak için yerleşik close()
işlevini kullanın:
close(ch)
Bir kanalı kapattığınızda, verilerin bir daha o kanala gönderilmeymeyeceğini söylemiş olursunuz. Kapalı bir kanala veri göndermeye çalışırsanız program paniğe kapılır. Kapalı bir kanaldan veri almaya çalışırsanız gönderilen tüm verileri okuyabilirsiniz. Sonraki her "okuma" sıfır değeri döndürür.
Şimdi daha önce oluşturduğumuz programa geri dönelim ve uyku işlevini kaldırmak için kanalları kullanalım. İlk olarak işlevde main
aşağıdaki gibi bir dize kanalı oluşturalım:
ch := make(chan string)
Şimdi uyku çizgisini time.Sleep(3 * time.Second)
kaldıralım.
Artık goroutinler arasında iletişim kurmak için kanalları kullanabiliriz. Sonucu işlevde checkAPI
yazdırmak yerine kodumuzu yeniden düzenleyelim ve bu iletiyi kanal üzerinden gönderelim. Bu işlevden kanalı kullanmak için, kanalı parametre olarak eklemeniz gerekir. İşlev aşağıdaki checkAPI
gibi görünmelidir:
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
Herhangi bir metni yazdırmak istemediğimiz için işlevini kullanmak fmt.Sprintf
zorunda olduğumuza dikkat edin, yalnızca biçimlendirilmiş metni kanal boyunca gönderin. Ayrıca, veri göndermek için kanal değişkeninin <-
ardından işlecini kullandığımıza da dikkat edin.
Şimdi kanal değişkenini main
göndermek ve yazdırmak üzere verileri almak için işlevini değiştirmeniz gerekir, örneğin:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
Kanal, kanaldaki <-
verileri okumak istediğimizi söylemeden önce operatörü nasıl kullandığımıza dikkat edin.
Programı yeniden çalıştırdığınızda şuna benzer bir çıkış görürsünüz:
ERROR: https://api.somewhereintheinternet.com/ is down!
Done! It took 0.007401217 seconds!
En azından uyku işlevini çağırmadan çalışıyor, değil mi? Ama yine de istediğimizi yapmıyor. Yalnızca goroutinlerden birinin çıkışını görüyoruz ve beş tane oluşturduk. Sonraki bölümde bu programın neden bu şekilde çalıştığını görelim.
Kaldırılmayan kanallar
İşlevi make()
kullanarak bir kanal oluşturduğunuzda, varsayılan davranış olan kaldırılmamış bir kanal oluşturursunuz. Kaldırılan kanallar, verileri almaya hazır biri olana kadar gönderme işlemini engeller. Daha önce de söylediğimiz gibi gönderme ve alma işlemleri engelliyor. Bu engelleme işlemi, önceki bölümdeki programın ilk iletiyi alır almaz durdurulmasının da nedenidir.
Kanaldan okuduğu ve bazı verilerin gelmesini beklediği için programın engellendiğini fmt.Print(<-ch)
söyleyerek başlayabiliriz. Bir şey içerdiği anda, sonraki satırla devam eder ve program tamamlar.
Diğer goroutinlere ne oldu? Hala çalışıyor ama artık kimse dinlemiyor. Program erken bittiğinden bazı goroutinler veri gönderemedi. Bu noktayı kanıtlamak için şunun gibi başka bir fmt.Print(<-ch)
ekleyelim:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
Programı yeniden çalıştırdığınızda şuna benzer bir çıkış görürsünüz:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
Done! It took 0.263611711 seconds!
Şimdi iki API'nin çıkışını gördüğünüze dikkat edin. Daha fazla fmt.Print(<-ch)
satır eklemeye devam ederseniz kanala gönderilen tüm verileri okursunuz. Peki daha fazla veri okumaya çalıştığınızda artık kimse veri göndermiyorsa ne olur? Örnek olarak şöyle bir örnek verilmiştir:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
Programı yeniden çalıştırdığınızda şuna benzer bir çıkış görürsünüz:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://dev.azure.com is up and running!
Çalışıyor, ancak program bitmiyor. Son yazdırma satırı, veri almayı beklediğinden bu satırı engelliyor. Programı gibi Ctrl+C
bir komutla kapatmanız gerekir.
Önceki örnekte yalnızca veri okuma ve veri alma işlemlerinin engellediği kanıtlanıyor. Bu sorunu çözmek için kodu bir for
döngü olarak değiştirebilir ve yalnızca gönderdiğinizden emin olduğunuz verileri alabilirsiniz, örneğin:
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
Sürümünüzde bir sorun olması durumunda programın son sürümü aşağıdadır:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
Programı yeniden çalıştırdığınızda şuna benzer bir çıkış görürsünüz:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
Done! It took 0.602099714 seconds!
Program yapması gereken şeyi yapıyor. Artık bir uyku işlevi kullanmıyor, kanalları kullanıyorsunuz. Eşzamanlılık kullanmadığımız zamanlarda neredeyse 2 saniye yerine tamamlanmasının yaklaşık 600 ms sürdüğüne de dikkat edin.
Son olarak, çıkarılmamış kanalların gönderme ve alma işlemlerini eşitlediğini söyleyebiliriz. Eşzamanlılık kullanıyor olsanız bile, iletişim zaman uyumludur.