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.
Ushbu bo‘lim mundarijasi
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 #
package main
import "fmt"
func main() {
kanal := make(chan string)
go func() {
kanal <- "salom gorutinadan"
}()
xabar := <-kanal
fmt.Println(xabar)
}
salom gorutinadan
Ikki amal bor va ikkalasi ham <- strelkasi bilan yoziladi:
| Yozuv | Ma'nosi |
|---|---|
kanal <- qiymat | Kanalga yuborish |
qiymat := <-kanal | Kanaldan olish |
Strelka har doim kanaldan uzoqlashadi yoki unga qaraydi - yo'nalishni shundan bilib olish oson.
Bufersiz kanal - bu uchrashuv #
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")
}
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.
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 #
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)
}
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.
Uch qoida bor va ularni buzish vahima keltiradi:
- Yopilgan kanalga yozib bo'lmaydi:
panic: send on closed channel - Kanalni ikki marta yopib bo'lmaydi:
panic: close of closed channel nilkanal 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 #
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)
}
}
2 2
birinchi
1 2
ikkinchi
uchinchi
make(chan string, 2) - ikkita joyi bor kanal. Yuboruvchi buferi
to'lmaguncha bloklanmaydi.
| Tur | Xatti-harakati |
|---|---|
make(chan T) | Yuborish oluvchini kutadi |
make(chan T, n) | n tagacha bloklanmaydi |
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 #
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)
}
}
}
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.
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 #
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")
}
}
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 #
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))
}
15
Funksiya imzosida kanalning yo'nalishini cheklash mumkin:
| Yozuv | Ma'nosi |
|---|---|
chan int | O'qish ham, yozish ham |
chan<- int | Faqat yozish |
<-chan int | Faqat o'qish |
Bu hujjat ham, himoya ham: yigindi funksiyasi kanalga
tasodifan yozib yubora olmaydi - kompilyator ruxsat bermaydi.
Ishchilar hovuzi #
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)
}
[1 4 9 16 25]
Bu Go dagi eng ko'p ishlatiladigan naqshlardan biri:
- Uchta ishchi bitta
vazifalarkanalidan o'qiydi. - Har biri natijani
natijalarkanaliga yozadi. close(vazifalar)ishchilarga "boshqa yo'q" deb aytadi.kutuvchi.Wait()hammasi tugashini kutadi.- Shundan keyingina
natijalaryopiladi.
Tartib kafolatlanmaydi - qaysi ishchi qaysi vazifani olishi
noma'lum. Shuning uchun oxirida slices.Sort.
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 #
package main
import "fmt"
func main() {
kanal := make(chan int, 1)
kanal <- 1
fmt.Println(<-kanal)
fmt.Println("bloklanmadi, chunki bufer bor edi")
}
1
bloklanmadi, chunki bufer bor edi
Yuqoridagi kodda buferni olib tashlang - make(chan int) deb
yozing. Dastur darhol to'xtaydi:
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 #
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)
}
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.
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.
- Bufersiz kanal orqali bitta xabar uzating.
- Gorutinadan uchta qiymat yuborib,
rangebilan oling. - Kanalni yopib, comma-ok bilan holatini tekshiring.
- Yopilgan kanalga yozib, vahimani ko'ring.
- Buferli kanal yasab,
lenvacapni kuzating. selectbilan ikki kanaldan tezrog'ini tanlang.time.Afterbilan kutish muddati qo'ying.- Yo'nalishli kanal bilan funksiya yozing.
- Uchta ishchili hovuz tuzing.
- 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
falseberadi;rangeesa o'zi to'xtaydi. selecttayyor bo'lgan shoxni tanlaydi;time.Afterbilan birga kutish muddatini beradi.chan<-va<-chanyo'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
Mutexga o'ting.
Keyingi bo'limda hammasini birlashtirib, amaliy loyiha yozamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.