65 / 73

Wait groups

Oldingi bo'limda biz goroutine'lar va kanallarning asosiy mexanizmlari bilan tanishdik. Endi esa bir vaqtda ishlovchi goroutine'larni xavfsiz sinxronlash (synchronization) san'atini o'rganamiz!

Bob mundarijasi:


WaitGroup nima va u qanday ishlaydi

sync.WaitGroup — bu bir nechta goroutine'lar to'liq tugagunga qadar dasturning asosiy oqimini to'xtatib turish uchun xizmat qiluvchi qulay hisoblagichdir.

U uchta asosiy metodga ega:

  1. Add(delta int) — hisoblagichni delta miqdoriga oshiradi (yoki kamaytiradi).
  2. Done() — hisoblagichni 1 ga kamaytiradi (aslida bu Add(-1) ning o'zi).
  3. Wait() — hisoblagich 0 ga teng bo'lguncha joriy goroutine'ni bloklaydi.
example.go
package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // ish tugagach hisoblagichni kamaytirish kafolati
	fmt.Printf("Ishchi #%d ishni boshladi
", id)
	time.Sleep(100 * time.Millisecond)
	fmt.Printf("Ishchi #%d tugatdi
", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1) // goroutine boshlanishidan OLDIN chaqiriladi!
		go worker(i, &wg)
	}

	wg.Wait() // barcha 3 ta ishchi tugashini kutadi
	fmt.Println("Barcha ishchilar muvaffaqiyatli yakunlandi!")
}

WaitGroup ning ichki dunyosi

sync.WaitGroup Go standart kutubxonasida qanday tuzilgan? Uning ichida oddiy atomic hisoblagich va operatsion tizim semafori joylashgan:

  • Hisoblagich yuqori 32-bitida kutilayotgan goroutine'lar sonini saqlaydi;
  • Quyi 32-bitida esa Wait() chaqirib kutib turgan goroutine'lar soni turadi;
  • Hisoblagich 0 ga tushganda, semafor orqali Wait() chaqirgan barcha oqimlar bir vaqtda uyg'otiladi.

Qiymat va ko'rsatkich

[!CAUTION] sync.WaitGroup obyektidan nusxa ko'chirib bo'lmaydi! Agar siz uni funksiyaga qiymat (sync.WaitGroup) sifatida uzatsangiz, yangi nusxa yaratiladi va main dagi WaitGroup hech qachon kamaymaydi. Natijada deadlock yuz beradi!

Har doim ko'rsatkich (*sync.WaitGroup) orqali uzating:

example.go
func worker(wg *sync.WaitGroup) // TO'G'RI
func worker(wg sync.WaitGroup)  // XATO! Nusxa olinadi!

Enkapsulyatsiya

Eng yaxshi dasturlash amaliyoti shuki, ishchi funksiya WaitGroup haqida umuman bilmasligi kerak. Concurrency mantig'ini biznes mantig'iga aralashtirmang:

example.go
func doWork(id int) {
	// toza biznes mantiq
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			doWork(id)
		}(i)
	}
	wg.Wait()
}

Go 1.25+ da esa buni yanada qulayroq wg.Go(func() { doWork(id) }) orqali yozish mumkin.


Wait dan keyin Add qilish xatosi

wg.Wait() kutilayotgan paytda boshqa goroutinedan wg.Add(1) chaqirish qat'iyan man etiladi va bu panic keltirib chiqaradi. Barcha Add() amallari Wait() chaqirilgunga qadar yoki yangi kutish sikli boshlanishidan oldin to'liq bajarilgan bo'lishi shart.


Topshiriq: Parallel yuklash simulyatsiyasi

3 ta faylni parallel yuklab, ularning hajmlarini hisoblovchi dastur tuzing. sync.WaitGroup orqali barcha yuklashlar tugashini kuting.

example.go
package main

import (
	"fmt"
	"sync"
	"time"
)

func downloadFile(name string) int {
	time.Sleep(50 * time.Millisecond)
	return len(name) * 1024
}

func main() {
	files := []string{"video.mp4", "music.mp3", "doc.pdf"}
	var wg sync.WaitGroup

	// TODO: Har bir fayl uchun alohida goroutine oching va WaitGroup orqali kuting
	// wg.Wait()

	fmt.Println("Barcha fayllar yuklandi!")
}

Xulosa va keyingi qadamlar

sync.WaitGroup goroutine'lar guruhining ishini yakunlashini kutish uchun eng optimal va tezkor vositadir.

Keyingi mavzu: Data races (Ma'lumotlar poygasi va sync.Mutex)!