19-bo‘lim

Kanallar va select

Kanal yaratish va yuborish, buferli va bufersiz farqi, yopish va range, select, kutish muddati, ishchilar hovuzi hamda boshi berk holat.

🕑 15 daqiqa o‘qish 📄 976 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Birinchi kanal
  2. Bufersiz kanal - bu uchrashuv
  3. close va range
  4. Buferli kanal
  5. select
  6. Kutish muddati
  7. Yo'nalishli kanallar
  8. Ishchilar hovuzi
  9. Boshi berk holat
  10. sync.Once
  11. Xulosa

Gorutinalar bir-biriga ma'lumot uzatishi kerak. Buning uchun Go da kanal bor.

Go jamoasining mashhur shiori shunday: *"Xotirani bo'lishib aloqa qilmang - aloqa qilib xotirani bo'lishing."*

Birinchi kanal #

Go
package main

import "fmt"

func main() {
    kanal := make(chan string)

    go func() {
        kanal <- "salom gorutinadan"
    }()

    xabar := <-kanal
    fmt.Println(xabar)
}
Natija
salom gorutinadan

Ikki amal bor va ikkalasi ham <- strelkasi bilan yoziladi:

YozuvMa'nosi
kanal <- qiymatKanalga yuborish
qiymat := <-kanalKanaldan olish

Strelka har doim kanaldan uzoqlashadi yoki unga qaraydi - yo'nalishni shundan bilib olish oson.

Bufersiz kanal - bu uchrashuv #

Go
package main

import (
    "fmt"
    "sync"
)

func main() {
    kanal := make(chan int)
    var kutuvchi sync.WaitGroup

    kutuvchi.Add(1)
    go func() {
        defer kutuvchi.Done()
        for i := 1; i <= 3; i++ {
            kanal <- i * 10
        }
        close(kanal)
    }()

    for qiymat := range kanal {
        fmt.Println("olindi:", qiymat)
    }

    kutuvchi.Wait()
    fmt.Println("tugadi")
}
Natija
olindi: 10
olindi: 20
olindi: 30
tugadi

Bufersiz kanalda yuborish bloklanadi, toki kimdir olmaguncha. Olish ham bloklanadi, toki kimdir yubormaguncha.

Shuning uchun bufersiz kanal - bu ikki gorutinaning uchrashuv nuqtasi. U ma'lumot uzatadi va bir vaqtda ularni tenglashtiradi.

Bu tartibni kafolatlaydi

18-bo'limdagi misollarda natijani saralash kerak edi, chunki gorutinalar tartibi noma'lum edi.

Bu yerda esa saralash kerak emas. Bitta gorutina ketma-ket yubormoqda, main esa ketma-ket olmoqda - kanal tartibni o'zi saqlaydi.

Kanal - navbat: birinchi kirgan birinchi chiqadi.

close va range #

Go
package main

import "fmt"

func main() {
    kanal := make(chan int, 3)

    kanal <- 1
    kanal <- 2
    close(kanal)

    q1, ochiq1 := <-kanal
    fmt.Println(q1, ochiq1)

    q2, ochiq2 := <-kanal
    fmt.Println(q2, ochiq2)

    q3, ochiq3 := <-kanal
    fmt.Println(q3, ochiq3)
}
Natija
1 true
2 true
0 false

Yopilgan kanaldan qolgan qiymatlarni o'qish mumkin. Ular tugagach, kanal darhol nol qiymat va false qaytaradi.

Bu 10-bo'limdagi comma-ok shaklining uchinchi ko'rinishi.

range esa buni o'zi hal qiladi: kanal yopilganda sikl tugaydi.

Kanalni yuboruvchi yopadi

Uch qoida bor va ularni buzish vahima keltiradi:

  1. Yopilgan kanalga yozib bo'lmaydi: panic: send on closed channel
  2. Kanalni ikki marta yopib bo'lmaydi: panic: close of closed channel
  3. nil kanal abadiy bloklaydi.

Shuning uchun qoida oddiy: kanalni faqat yuboruvchi yopadi, va faqat bitta marta.

Oluvchi hech qachon yopmaydi - u kanalga yana kim yozishini bilmaydi.

Yopish majburiy ham emas: agar range ishlatmasangiz va gorutinalar tugab ketsa, kanal o'z-o'zidan tozalanadi.

Buferli kanal #

Go
package main

import "fmt"

func main() {
    kanal := make(chan string, 2)

    kanal <- "birinchi"
    kanal <- "ikkinchi"

    fmt.Println(len(kanal), cap(kanal))

    fmt.Println(<-kanal)
    fmt.Println(len(kanal), cap(kanal))

    kanal <- "uchinchi"
    close(kanal)

    for q := range kanal {
        fmt.Println(q)
    }
}
Natija
2 2
birinchi
1 2
ikkinchi
uchinchi

make(chan string, 2) - ikkita joyi bor kanal. Yuboruvchi buferi to'lmaguncha bloklanmaydi.

TurXatti-harakati
make(chan T)Yuborish oluvchini kutadi
make(chan T, n)n tagacha bloklanmaydi
Qaysi birini tanlash

Odatiy tanlov - bufersiz. U sodda va xatolarni tezroq ko'rsatadi: agar hech kim olmayotgan bo'lsa, dastur darhol bloklanadi va buni sezasiz.

Bufer esa muammoni yashirishi mumkin: dastur bir muddat ishlab turadi, keyin bufer to'lganda kutilmaganda to'xtaydi.

Bufer haqiqatan foydali bo'lgan joy - ishlab chiqaruvchi va iste'molchi tezligi notekis bo'lganda, yoki quyidagi ishchilar hovuzida natijalarni yig'ishda.

select #

Go
package main

import (
    "fmt"
    "time"
)

func main() {
    tez := make(chan string)
    sekin := make(chan string)

    go func() {
        time.Sleep(10 * time.Millisecond)
        tez <- "tez kanal"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        sekin <- "sekin kanal"
    }()

    for range 2 {
        select {
        case xabar := <-tez:
            fmt.Println("birinchi:", xabar)
        case xabar := <-sekin:
            fmt.Println("ikkinchi:", xabar)
        }
    }
}
Natija
birinchi: tez kanal
ikkinchi: sekin kanal

select - kanallar uchun switch. U tayyor bo'lgan birinchi shoxni tanlaydi.

Bir nechta shox bir vaqtda tayyor bo'lsa, Go ulardan tasodifiy birini oladi.

Bu misol vaqtga tayangan

Yuqoridagi natija 10ms va 200ms farqiga asoslangan. Bu darslik uchun ishonchli, lekin haqiqiy kodda vaqtga tayanmang.

Agar kompyuter juda band bo'lsa, tartib o'zgarishi mumkin. Ishonchli kodda tartib kerak bo'lsa, uni kanallar va WaitGroup bilan aniq ta'minlang, Sleep bilan emas.

Kutish muddati #

Go
package main

import (
    "fmt"
    "time"
)

func sekinIsh(natija chan<- string) {
    time.Sleep(500 * time.Millisecond)
    natija <- "tugadi"
}

func main() {
    natija := make(chan string, 1)
    go sekinIsh(natija)

    select {
    case q := <-natija:
        fmt.Println("javob:", q)
    case <-time.After(50 * time.Millisecond):
        fmt.Println("muddat tugadi")
    }
}
Natija
muddat tugadi

time.After - berilgan vaqtdan keyin qiymat yuboradigan kanal qaytaradi.

select bilan birga u kutish muddati naqshini beradi: yo ish tugaydi, yo vaqt tugaydi.

E'tibor bering: natija kanali buferli. Aks holda muddat tugagandan keyin gorutina abadiy bloklanib qolardi - 18-bo'limdagi gorutina oqishi.

Yo'nalishli kanallar #

Go
package main

import "fmt"

func ishlabChiqar(chiqish chan<- int, soni int) {
    for i := 1; i <= soni; i++ {
        chiqish <- i
    }
    close(chiqish)
}

func yigindi(kirish <-chan int) int {
    jami := 0
    for q := range kirish {
        jami += q
    }
    return jami
}

func main() {
    kanal := make(chan int)
    go ishlabChiqar(kanal, 5)
    fmt.Println(yigindi(kanal))
}
Natija
15

Funksiya imzosida kanalning yo'nalishini cheklash mumkin:

YozuvMa'nosi
chan intO'qish ham, yozish ham
chan<- intFaqat yozish
<-chan intFaqat o'qish

Bu hujjat ham, himoya ham: yigindi funksiyasi kanalga tasodifan yozib yubora olmaydi - kompilyator ruxsat bermaydi.

Ishchilar hovuzi #

Go
package main

import (
    "fmt"
    "slices"
    "sync"
)

func ishchi(id int, vazifalar <-chan int, natijalar chan<- int,
    kutuvchi *sync.WaitGroup) {
    defer kutuvchi.Done()
    for v := range vazifalar {
        natijalar <- v * v
    }
}

func main() {
    vazifalar := make(chan int, 10)
    natijalar := make(chan int, 10)
    var kutuvchi sync.WaitGroup

    for i := 1; i <= 3; i++ {
        kutuvchi.Add(1)
        go ishchi(i, vazifalar, natijalar, &kutuvchi)
    }

    for v := 1; v <= 5; v++ {
        vazifalar <- v
    }
    close(vazifalar)

    kutuvchi.Wait()
    close(natijalar)

    yigilgan := []int{}
    for n := range natijalar {
        yigilgan = append(yigilgan, n)
    }
    slices.Sort(yigilgan)
    fmt.Println(yigilgan)
}
Natija
[1 4 9 16 25]

Bu Go dagi eng ko'p ishlatiladigan naqshlardan biri:

  1. Uchta ishchi bitta vazifalar kanalidan o'qiydi.
  2. Har biri natijani natijalar kanaliga yozadi.
  3. close(vazifalar) ishchilarga "boshqa yo'q" deb aytadi.
  4. kutuvchi.Wait() hammasi tugashini kutadi.
  5. Shundan keyingina natijalar yopiladi.

Tartib kafolatlanmaydi - qaysi ishchi qaysi vazifani olishi noma'lum. Shuning uchun oxirida slices.Sort.

Natijalar kanalini erta yopmang

close(natijalar) ni kutuvchi.Wait() dan oldin yozsangiz, dastur vahima bilan to'xtaydi: hali ishlayotgan ishchi yopilgan kanalga yozmoqchi bo'ladi.

Tartib har doim shunday: avval kiruvchi kanalni yop, keyin ishchilarni kut, keyin chiquvchi kanalni yop.

Boshi berk holat #

Go
package main

import "fmt"

func main() {
    kanal := make(chan int, 1)

    kanal <- 1
    fmt.Println(<-kanal)

    fmt.Println("bloklanmadi, chunki bufer bor edi")
}
Natija
1
bloklanmadi, chunki bufer bor edi
Bufersiz kanalda bu dasturni to'xtatadi

Yuqoridagi kodda buferni olib tashlang - make(chan int) deb yozing. Dastur darhol to'xtaydi:

Natija
fatal error: all goroutines are asleep - deadlock!

Sabab: kanal <- 1 oluvchini kutadi, lekin oluvchi keyingi qatorda - main esa allaqachon bloklangan. Hech kim harakat qilolmaydi.

Yaxshi xabar: Go bu holatni o'zi sezadi va aniq xabar beradi. Ko'p tillarda dastur shunchaki abadiy osilib qolardi.

Yomon xabar: Go faqat hamma gorutina uxlayotganini sezadi. Agar bitta gorutina cheksiz siklda aylanayotgan bo'lsa, boshi berk holat sezilmaydi.

sync.Once #

Go
package main

import (
    "fmt"
    "sync"
)

var (
    bir      sync.Once
    sozlama  string
    tayyorla = func() {
        sozlama = "bir marta tayyorlandi"
    }
)

func Ol() string {
    bir.Do(tayyorla)
    return sozlama
}

func main() {
    var kutuvchi sync.WaitGroup
    natijalar := make([]string, 5)

    for i := range 5 {
        kutuvchi.Add(1)
        go func() {
            defer kutuvchi.Done()
            natijalar[i] = Ol()
        }()
    }
    kutuvchi.Wait()

    fmt.Println(natijalar[0])
    hammasiBir := true
    for _, n := range natijalar {
        if n != natijalar[0] {
            hammasiBir = false
        }
    }
    fmt.Println(hammasiBir)
}
Natija
bir marta tayyorlandi
true

sync.Once funksiyani aniq bir marta bajaradi, nechta gorutina chaqirishidan qat'i nazar.

Bu qimmat sozlamani birinchi kerak bo'lganda tayyorlash uchun ishlatiladi.

Qachon kanal, qachon Mutex

Ikkalasi ham parallellik uchun, lekin vazifasi boshqa:

  • Kanal - ma'lumotni bir gorutinadan boshqasiga uzatish kerak bo'lganda. Vazifa navbati, natijalar, signal.
  • Mutex - bir nechta gorutina bitta ma'lumotni birga ishlatganda. Kesh, hisoblagich, umumiy holat.

Go jamoasining maslahati: kanaldan boshlang. Agar kod chalkashroq bo'lib ketsa, sync paketiga o'ting.

Ba'zan eng oddiy yechim - sync.Mutex bilan himoyalangan oddiy map.

Amaliy topshiriq
  1. Bufersiz kanal orqali bitta xabar uzating.
  2. Gorutinadan uchta qiymat yuborib, range bilan oling.
  3. Kanalni yopib, comma-ok bilan holatini tekshiring.
  4. Yopilgan kanalga yozib, vahimani ko'ring.
  5. Buferli kanal yasab, len va cap ni kuzating.
  6. select bilan ikki kanaldan tezrog'ini tanlang.
  7. time.After bilan kutish muddati qo'ying.
  8. Yo'nalishli kanal bilan funksiya yozing.
  9. Uchta ishchili hovuz tuzing.
  10. Bufersiz kanalda boshi berk holatni ataylab yuzaga keltiring.

Xulosa #

  • Kanal - gorutinalar orasida ma'lumot uzatish vositasi; <- strelkasi yo'nalishni ko'rsatadi.
  • Bufersiz kanal - uchrashuv nuqtasi: yuborish oluvchini kutadi.
  • Kanal tartibni saqlaydi - navbat kabi ishlaydi.
  • Kanalni faqat yuboruvchi va faqat bir marta yopadi.
  • Yopilgan kanaldan o'qish nol qiymat va false beradi; range esa o'zi to'xtaydi.
  • select tayyor bo'lgan shoxni tanlaydi; time.After bilan birga kutish muddatini beradi.
  • chan<- va <-chan yo'nalishni cheklaydi - hujjat ham, himoya ham.
  • Ishchilar hovuzida tartib: kiruvchini yop, ishchilarni kut, chiquvchini yop.
  • Go boshi berk holatni sezadi, lekin faqat hamma gorutina uxlayotgan bo'lsa.
  • Kanaldan boshlang, kerak bo'lsa Mutex ga o'ting.

Keyingi bo'limda hammasini birlashtirib, amaliy loyiha yozamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.