Arabelleğe alınan kanallar hakkında bilgi edinin

Tamamlandı

Öğrendiğiniz gibi kanallar varsayılan olarak kaldırılmış durumdadır. Bu, gönderme işlemini yalnızca alma işlemi varsa kabul ettikleri anlamına gelir. Aksi takdirde, program sonsuza kadar beklerken engellenir.

Goroutin'ler arasında bu tür bir eşitlemeye ihtiyaç duyduğunuz zamanlar vardır. Ancak, eşzamanlılık uygulamanız gereken ve goroutinlerin birbirleriyle iletişim kurma biçimini kısıtlamanız gerekmeyen zamanlar olabilir.

Arabelleğe alınan kanallar, arabelleğe alınmış bir kanal kuyruk gibi davrandığından programı engellemeden veri gönderir ve alır. Kanalı oluştururken bu kuyruğun boyutunu şu şekilde sınırlayabilirsiniz:

ch := make(chan string, 10)

Kanala her bir şey gönderdiğinizde, öğesi kuyruğa eklenir. Ardından bir alma işlemi, öğesini kuyruktan kaldırır. Kanal dolduğunda, herhangi bir gönderme işlemi yalnızca verileri tutacak alan olana kadar bekler. Buna karşılık, kanal boşsa ve bir okuma işlemi varsa, okunacak bir şey olana kadar engellenir.

Arabelleğe alınan kanalları anlamak için basit bir örnek aşağıda verilmiştir:

package main

import (
    "fmt"
)

func send(ch chan string, message string) {
    ch <- message
}

func main() {
    size := 4
    ch := make(chan string, size)
    send(ch, "one")
    send(ch, "two")
    send(ch, "three")
    send(ch, "four")
    fmt.Println("All data sent to the channel ...")

    for i := 0; i < size; i++ {
        fmt.Println(<-ch)
    }

    fmt.Println("Done!")
}

Programı çalıştırdığınızda aşağıdaki çıkışı görürsünüz:

All data sent to the channel ...
one
two
three
four
Done!

Burada farklı bir şey yapmadığımızı ve haklı olduğunu söyleyebilirsin. Ancak değişkeni daha düşük bir sayıyla size değiştirdiğinizde (daha yüksek bir sayıyla bile deneyebilirsiniz) aşağıdaki gibi ne zaman olacağını görelim:

size := 2

Programı yeniden çalıştırırken aşağıdaki hatayı alırsınız:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.send(...)
        /Users/developer/go/src/concurrency/main.go:8
main.main()
        /Users/developer/go/src/concurrency/main.go:16 +0xf3
exit status 2

Bunun nedeni, işleve yapılan çağrıların send sıralı olmasıdır. Yeni bir goroutine oluşturmuyorsunuz. Bu nedenle kuyruğa alınacak bir şey yoktur.

Kanallar goroutinlere derinden bağlıdır. Kanaldan veri alan başka bir goroutine olmadan, programın tamamı sonsuza kadar bir bloğa girebilir. Gördüğünüz gibi, bu gerçekleşiyor.

Şimdi ilginç bir şey yapalım! Son iki çağrı için bir goroutine oluşturacağız (ilk iki çağrı arabelleğe düzgün bir şekilde sığar) ve dört kez for döngüsü çalıştırması yapacağız. Kod şu şekildedir:

func main() {
    size := 2
    ch := make(chan string, size)
    send(ch, "one")
    send(ch, "two")
    go send(ch, "three")
    go send(ch, "four")
    fmt.Println("All data sent to the channel ...")

    for i := 0; i < 4; i++ {
        fmt.Println(<-ch)
    }

    fmt.Println("Done!")
}

Programı çalıştırdığınızda beklendiği gibi çalışır. Kanalları kullanırken her zaman goroutinler kullanmanızı öneririz.

Şimdi ihtiyacınız olandan daha fazla öğe içeren arabelleğe alınan kanal oluşturduğunuz durumu test edelim. API'leri denetlemek ve 10 boyutunda arabelleğe alınan bir kanal oluşturmak için daha önce kullandığımız örneği kullanacağız:

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, 10)

    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ı çalıştırdığınızda, öncekiyle aynı çıkışı elde edersiniz. Kanalın boyutunu daha düşük veya daha yüksek sayılarla değiştirerek oyun oynayabilirsiniz ve program çalışmaya devam eder.

Arabelleğe alınan kanallarla arabelleğe alınamayan kanal karşılaştırması

Bu noktada, bir türün veya başka bir türün ne zaman kullanılacağını merak ediyor olabilirsiniz. Her şey, iletişimin goroutinler arasında nasıl akmasını istediğinize bağlıdır. Kaldırılan kanallar zaman uyumlu bir şekilde iletişim kurar. Her veri gönderdiğinizde, birisi kanaldan okuyana kadar programın engelleneceğini garanti eder.

Buna karşılık, arabelleğe alınan kanallar gönderme ve alma işlemlerini birbirinden kaldırır. Bir programı engellemez, ancak dikkatli olmanız gerekir çünkü kilitlenmeye neden olabilirsiniz (daha önce gördüğünüz gibi). Sıkıştırılmamış kanalları kullandığınızda, eşzamanlı olarak kaç goroutin çalıştırabileceğinizi denetleyebilirsiniz. Örneğin, bir API'ye çağrı yapıyor olabilirsiniz ve her saniye kaç çağrı gerçekleştirebileceğinizi denetlemek isteyebilirsiniz. Aksi takdirde engellenebilirsiniz.

Kanal yol tarifleri

Go'daki kanallar başka bir ilginç özelliğe sahiptir. Kanalları bir işlev için parametre olarak kullandığınızda, kanalın veri göndermeyi mi yoksa almayıamaçladığını belirtebilirsiniz. Programınız büyüdükçe çok fazla işleviniz olabilir ve her kanalın amacını düzgün bir şekilde kullanmak için belgeleseniz iyi olur. Ya da bir kitaplık yazıyor ve veri tutarlılığını korumak için kanalı salt okunur olarak kullanıma açmak istiyorsunuz.

Kanalın yönünü tanımlamak için, verileri okurken veya alırken yaptığınıza benzer bir şekilde yaparsınız. Ancak bunu, kanalı bir işlev parametresinde bildirirken yaparsınız. Kanal türünü bir işlevde parametre olarak tanımlamak için söz dizimi şöyledir:

chan<- int // it's a channel to only send data
<-chan int // it's a channel to only receive data

Yalnızca alma amaçlı bir kanal üzerinden veri gönderdiğinizde, programı derlerken hata alırsınız.

Biri verileri okuyan, diğeri de veri gönderen iki işlev örneği olarak aşağıdaki programı kullanalım:

package main

import "fmt"

func send(ch chan<- string, message string) {
    fmt.Printf("Sending: %#v\n", message)
    ch <- message
}

func read(ch <-chan string) {
    fmt.Printf("Receiving: %#v\n", <-ch)
}

func main() {
    ch := make(chan string, 1)
    send(ch, "Hello World!")
    read(ch)
}

Programı çalıştırdığınızda aşağıdaki çıkışı görürsünüz:

Sending: "Hello World!"
Receiving: "Hello World!"

Program, her işlevde her kanalın amacını net bir şekilde açıklar. Amacı yalnızca veri almak olan bir kanalda veri göndermek için bir kanal kullanmaya çalışırsanız derleme hatası alırsınız. Örneğin, şöyle bir şey yapmayı deneyin:

func read(ch <-chan string) {
    fmt.Printf("Receiving: %#v\n", <-ch)
    ch <- "Bye!"
}

Programı çalıştırdığınızda aşağıdaki hatayı görürsünüz:

# command-line-arguments
./main.go:12:5: invalid operation: ch <- "Bye!" (send to receive-only type <-chan string)

Derleme hatası almak, kanalı kötüye kullanmaktan daha iyidir.

Çoğullama

Son olarak, anahtar sözcüğünü kullanarak aynı anda birden fazla kanalla nasıl etkileşim kuracaklarını select görelim. Bazen, birden çok kanalla çalışırken bir olayın gerçekleşmesini beklemek istersiniz. Örneğin, programınızın işlemekte olduğu verilerde bir anomali olduğunda işlemi iptal etmek için bir mantık ekleyebilirsiniz.

Deyim select , bir deyim gibi switch çalışır ancak kanallar için çalışır. İşlenmek üzere bir olay alana kadar programın yürütülmesini engeller. Birden fazla olay alırsa rastgele bir olay seçer.

deyiminin select önemli bir yönü, bir olayı işledikten sonra yürütmesini tamamlamasıdır. Daha fazla olayın gerçekleşmesini beklemek istiyorsanız döngü kullanmanız gerekebilir.

Şimdi aşağıdaki programı kullanarak nasıl çalıştığını görelim select :

package main

import (
    "fmt"
    "time"
)

func process(ch chan string) {
    time.Sleep(3 * time.Second)
    ch <- "Done processing!"
}

func replicate(ch chan string) {
    time.Sleep(1 * time.Second)
    ch <- "Done replicating!"
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go process(ch1)
    go replicate(ch2)

    for i := 0; i < 2; i++ {
        select {
        case process := <-ch1:
            fmt.Println(process)
        case replicate := <-ch2:
            fmt.Println(replicate)
        }
    }
}

Programı çalıştırdığınızda aşağıdaki çıkışı görürsünüz:

Done replicating!
Done processing!

İşlevin önce tamamlandığına replicate dikkat edin. Bu nedenle çıktısını ilk olarak terminalde görürsünüz. deyimi bir olay alır almaz sona erdiğinden select ana işlevin bir döngüsü vardır, ancak işlevin process bitmesi için hala bekliyoruz.