Go’da goroutine va kanallar: konkurentlik bo‘yicha qo‘llanma
Amaliyot3 daqInnovialabs tahririyati
Qisqacha
Goroutine — go orqali ishga tushadigan yengil funksiya, kanal esa unga ma’lumotni xavfsiz uzatish usuli; tugashini WaitGroup va context boshqaradi, aks holda goroutine’lar sizib ketadi.
Qisqa javob: Go’da konkurentlik qanday ishlaydi
Goroutine — Go runtime qolgan kod bilan parallel bajaradigan funksiya. U go kalit so‘zi bilan ishga tushadi va arzon: bir vaqtda minglab goroutine bo‘lishi odatiy hol.
Kanal — goroutine’lar ma’lumot almashadigan tiplangan quvur. Go tamoyili: xotirani bo‘lishib muloqot qilmang, muloqot orqali xotirani bo‘lishing.
func main() {
ch := make(chan string)
go func() {
ch <- "tayyor"
}()
fmt.Println(<-ch)
}
Kanaldan o‘qilmasa, main goroutine’dan oldin tugab, xabar yo‘qolardi.
Bufersiz va buferli kanallar
| Turi | Yaratish | Xatti-harakati |
|---|---|---|
| Bufersiz | make(chan int) | Yuborish kimdir o‘qimaguncha kutadi. Bu sinxronlash nuqtasi |
| Buferli | make(chan int, 10) | Yuborish faqat bufer to‘lganda bloklanadi |
Amaliy qoidalar:
- Bufersiz kanal — qabul qiluvchi qiymatni haqiqatan olgani muhim bo‘lganda.
- Buferli — tez ishlab chiqaruvchi va sekin iste’molchi orasidagi to‘lqinlarni yumshatish uchun. Bufer deadlock’ni tuzatmaydi, faqat kechiktiradi.
- Kanalni yuboruvchi yopadi, qabul qiluvchi emas. Yopiq kanaldan o‘qish nol qiymat qaytaradi, yopiq kanalga yuborish esa panic beradi.
for v := range chkanal yopilguncha o‘qiydi.
select: bir nechta hodisani kutish
select bir nechta kanaldan birinchi tayyor bo‘lganini kutadi. Odatiy qo‘llanilishi — taymaut:
select {
case res := <-results:
fmt.Println(res)
case <-time.After(2 * time.Second):
fmt.Println("taymaut")
}
default tarmog‘i selectni bloklamaydigan qiladi: hech bir kanal tayyor bo‘lmasa, darhol u bajariladi.
WaitGroup: goroutine’lar guruhini kutish
Natija kerak bo‘lmay, faqat tugashini kutish kerak bo‘lsa, sync.WaitGroupdan foydalaning:
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u)
}(url)
}
wg.Wait()
Addni goroutine ishga tushishidan oldin chaqiring, Doneni esa defer orqali — shunda erta chiqishda ham ishlaydi.
context: bekor qilish va muddatlar
context.Context bekor qilish signalini chaqiruvlar zanjiri bo‘ylab pastga uzatadi. Bu osilib qolgan goroutine’larga qarshi asosiy vosita:
func worker(ctx context.Context, jobs <-chan int) {
for {
select {
case <-ctx.Done():
return
case j, ok := <-jobs:
if !ok {
return
}
process(j)
}
}
}
Kontekstni context.WithTimeout yoki context.WithCancel bilan yarating va har doim defer cancel() yozing.
Goroutine sizishidan qanday qochish kerak
Sizish — abadiy bloklangan va hech qachon tugamaydigan goroutine. U xotirani bo‘shatmaydi. Ko‘p uchraydigan sabablar:
- Hech kim o‘qimaydigan kanalga yuborish, masalan chaqiruvchi kod taymautdan keyin. Yechim: 1 lik bufer yoki
ctx.Done()bilanselect. - Hech kim yopmaydigan kanaldan o‘qish. Kanal egasi kim va uni kim yopishini kelishib oling.
- Kontekstni tekshirmaydigan cheksiz sikl.
- Unutilgan
wg.Done()—Waitabadiy kutadi.
Tekshirish: testlarda runtime.NumGoroutine() yoki pprof profilerı goroutine’lar soni vaqt o‘tishi bilan o‘syaptimi, ko‘rsatadi.
Ko‘p uchraydigan xatolar
- Data race — bir nechta goroutine umumiy map yoki slice’ga yozganda. Testlarni
-racebayrog‘i bilan ishga tushiring. - Sikl o‘zgaruvchisini ushlab qolish. Go’ning yangi versiyalarida har bir iteratsiya o‘z o‘zgaruvchisini oladi, lekin eski kodda qiymatni argument sifatida uzatgan ma’qul.
- Har bir vazifaga cheklanmagan goroutine. Minglab vazifalar uchun goroutine soni qat’iy bo‘lgan worker pool’dan foydalaning.