Ce face sync.WaitGroup
Funcția main nu așteaptă goroutine-urile pornite cu go: când main se termină, programul se oprește, chiar dacă ele nu au terminat. sync.WaitGroup este un contor de sarcini: wg.Add(1) înainte de a porni fiecare goroutine, wg.Done() (de obicei cu defer) când goroutine-ul termină, iar wg.Wait() blochează până când contorul ajunge la zero.
Add se apelează înainte de go, nu în interiorul goroutine-ului; altfel Wait poate vedea contorul 0 și trece mai departe prea devreme. Din Go 1.25, wg.Go(f) face toți pașii deodată (vezi pagina dedicată).
WaitGroup doar așteaptă, nu adună rezultate și nu transmite erori. Rezultatele le pui într-un slice la poziția fiecărei sarcini (fiecare goroutine scrie doar în elementul lui) sau le trimiți pe un canal.
Parametri
| Parametru | Descriere |
|---|---|
delta |
Cât adaugi la contor; de obicei 1, sau numărul de sarcini (wg.Add(len(lista))). |
Ce returnează
Nimic. Dacă contorul devine negativ (mai multe Done decât Add), programul se oprește cu panică.
Exemple
Exemplul 1: Descărcări în paralel
Fiecare goroutine scrie doar în elementul lui din slice, deci nu e nevoie de mutex. Afișarea se face după Wait, în ordinea listei.
package main
import (
"fmt"
"strings"
"sync"
"time"
)
func descarca(fisier string) string {
time.Sleep(time.Duration(len(fisier)) * 5 * time.Millisecond) // durate diferite
return strings.ToUpper(fisier) + " (ok)"
}
func main() {
fisiere := []string{"factura.pdf", "poza.jpg", "contract.docx", "notite.txt"}
rezultate := make([]string, len(fisiere))
var wg sync.WaitGroup
for i, f := range fisiere {
wg.Add(1)
go func() {
defer wg.Done()
rezultate[i] = descarca(f)
}()
}
wg.Wait()
for _, r := range rezultate {
fmt.Println(r)
}
fmt.Println("Toate cele", len(fisiere), "descărcări s-au terminat")
}
Rezultat
FACTURA.PDF (ok)
POZA.JPG (ok)
CONTRACT.DOCX (ok)
NOTITE.TXT (ok)
Toate cele 4 descărcări s-au terminat
Exemplul 2: Suma pe bucăți, cu Add(n) o singură dată
Împărțim numerele în 4 bucăți, fiecare goroutine calculează suma bucății lui.
package main
import (
"fmt"
"sync"
)
func main() {
numere := make([]int, 1000)
for i := range numere {
numere[i] = i + 1
}
const bucati = 4
sume := make([]int, bucati)
marime := len(numere) / bucati
var wg sync.WaitGroup
wg.Add(bucati)
for b := range bucati {
go func() {
defer wg.Done()
for _, n := range numere[b*marime : (b+1)*marime] {
sume[b] += n
}
}()
}
wg.Wait()
total := 0
for _, s := range sume {
total += s
}
fmt.Println("Sume parțiale:", sume)
fmt.Println("Total:", total)
}
Rezultat
Sume parțiale: [31375 93875 156375 218875]
Total: 500500
Exemplul 3: Fără WaitGroup: main nu așteaptă
Programul se termină înainte ca goroutine-urile să apuce să scrie ceva. Uneori apare o linie, alteori niciuna.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
go fmt.Println("goroutine", i)
}
fmt.Println("main s-a terminat")
}
Rezultat posibil (diferă la fiecare rulare)
main s-a terminat
Greșeli frecvente
- Apelezi
wg.Add(1)în interiorul goroutine-ului.Waitpoate rula înainte și trece de contorul 0; apeleazăAddînainte dego. - Transmiți
WaitGroupunei funcții prin valoare (func lucru(wg sync.WaitGroup)).Donescade contorul copiei șiWaitrămâne blocat; transmite*sync.WaitGroup. - Uiți
Donecând funcția iese cu eroare. Punedefer wg.Done()pe prima linie a goroutine-ului.
Comenzi înrudite
sync.WaitGroup.Go: Pornește funcția f într-un goroutine nou și o adaugă în WaitGroup; înlocuiește perechea Add(1) + defer Done().sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().sync/atomic.Int64: Un int64 cu operații atomice ca metode (Add, Load, Store, Swap); varianta recomandată pentru contoare între goroutine.context.WithCancel: Creează un context copil și o funcție cancel; apelul cancel() anunță toate goroutine-urile care îl folosesc să se oprească.