Nega `time.Sleep()` yetarli emas?
Oldingi darsda (Goroutines) main() funksiyasi goroutine tugashini kutishi uchun **time.Sleep()**dan foydalangan edik:
go display("1-jarayon")
time.Sleep(time.Second * 1) // "taxminan yetarli" vaqt kutamiz
Bu usul ishlaydi, lekin ishonchsiz: agar goroutine kutilganidan sekinroq ishlasa, main() u tugamasdan yopilib qoladi; agar tezroq tugasa, dastur keraksiz vaqt behuda kutadi. Bizga aniq: "aynan shu goroutine(lar) tugaguncha kut, na ko'p, na kam" degan mexanizm kerak. Aynan shu vazifa uchun sync.WaitGroup ishlatiladi.
import "sync"
Add(), Done(), Wait()package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
// funksiya tugashi bilan "men tugadim" deb belgilaydi
defer wg.Done()
fmt.Printf("Worker %d ishni boshladi\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
// "yana bitta goroutine kutilyapti" deb belgilaymiz
wg.Add(1)
go worker(i, &wg)
}
// barcha Add() lar Done() bilan tenglashguncha shu yerda to'xtab turadi
wg.Wait()
fmt.Println("Barcha workerlar tugadi")
}
Ehtimoliy natija (goroutinelar tartibi har safar farq qilishi mumkin):
Worker 1 ishni boshladi
Worker 3 ishni boshladi
Worker 2 ishni boshladi
Barcha workerlar tugadi
E'tibor bering: "Barcha workerlar tugadi" qatori har doim eng oxirida chiqadi — chunki wg.Wait() aynan shuni kafolatlaydi, time.Sleep()dan farqli o'laroq.
[!IMPORTANT]
wg—main()ichida e'lon qilingan, lekinworker()funksiyasiga ko'rsatkich (*sync.WaitGroup) sifatida uzatilgan. Agar uni ko'rsatkichsiz (qiymat sifatida) uzatsangiz,worker()ichidawgning nusxasi bilan ishlaysiz — aslwgga hech qanday ta'sir qilmaydi vamain()abadiyWait()da qotib qoladi!
Ko'pincha goroutinelar natijasini keyinroq ishlatish kerak bo'ladi. Buning eng oddiy yo'li — har bir goroutine o'z natijasini oldindan tayyorlangan slaysning o'ziga tegishli indeksiga yozishi:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
results := make([]int, 5)
for i := 0; i < 5; i++ {
wg.Add(1)
// e'tibor bering: i qiymati funksiyaga ARGUMENT sifatida uzatilmoqda
go func(idx int) {
defer wg.Done()
results[idx] = idx * idx
}(i)
}
wg.Wait()
fmt.Println(results)
}
Natija:
[0 1 4 9 16]
Har bir goroutine faqat o'ziga tegishli results[idx] katakchasiga yozgani uchun, bu yerda bir nechta goroutine bitta umumiy o'zgaruvchini bir vaqtda o'zgartirmayapti — shuning uchun Mutex shart emas (keyingi darsda buning nima uchun muhimligini chuqur ko'ramiz).
[!NOTE]
go func(idx int) { ... }(i)qolipiga e'tibor bering:iqiymati anonim funksiyaga parametr sifatida uzatilmoqda. Bu — Goning eski versiyalarida (1.22'gacha) mashhur bo'lgan "sikl o'zgaruvchisini noto'g'ri ushlab olish" xatosidan himoyalanishning eng aniq va tushunarli usuli: 2022-yilgacha bo'lgan Go versiyalaridaforsikli o'zgaruvchisi barcha iteratsiyalar uchun bitta xotira katakchasini ishlatgan, shuning uchun goroutine ichidaiga to'g'ridan-to'g'ri murojaat qilish kutilmagan natijalarga olib kelardi. Go 1.22 versiyasidan boshlab bu muammo tilning o'zida hal qilindi (har bir iteratsiya endi o'z nusxasiga ega), lekinidx intkabi aniq parametr orqali uzatish hamon eng tushunarli va xavfsiz uslub hisoblanadi.
Add()ni noto'g'ri joyda chaqirishpackage main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
go func(id int) {
wg.Add(1) // XATO: Add() goroutine ICHIDA chaqirilmoqda!
defer wg.Done()
fmt.Println("Worker", id)
}(i)
}
// wg.Wait() Add() chaqirilishidan OLDIN ishga tushib ketishi mumkin
wg.Wait()
_ = time.Now()
fmt.Println("Tugadi")
}
[!WARNING]
wg.Add()ni har doim goroutine boshlanishidan oldin, asosiy (chaqiruvchi) goroutine'da chaqiring — hech qachon yangi goroutine'ning o'zi ichida emas! Aks holdawg.Wait()hali birorta hamAdd()chaqirilmasdan turib ishga tushib, darhol "hammasi tugadi" deb noto'g'ri xulosaga kelishi mumkin — bu esa dasturning ba'zida to'g'ri, ba'zida noto'g'ri ishlashiga (poyga holatiga) sabab bo'ladi.
| Vazifa | Metod |
|---|---|
| "Yana bitta goroutine kutilyapti" deb belgilash | wg.Add(1) (goroutine boshlanishidan oldin!) |
| Goroutine tugaganini bildirish | defer wg.Done() (goroutine ichida, birinchi qatorda) |
| Barchasi tugashini kutish | wg.Wait() |
[!NOTE]
WaitGroupfaqat "hammasi tugadimi?" degan savolga javob beradi — u goroutinelar orasida ma'lumot uzatish yoki umumiy resursni himoya qilish uchun mo'ljallanmagan. Ma'lumot uzatish uchun channel, umumiy resursni himoya qilish uchun esa keyingi darsda ko'radigansync.Mutexishlatiladi.