Temel kalıp
sync.WaitGroup çalışan goroutine'leri sayar. Add sayıyı artırır, Done azaltır, Wait sıfır olana kadar bloklanır.
Üç indirme eşzamanlı çalışır, bu yüzden program 30 yerine yaklaşık 10 ms sürer. Sonuçlar girdi sırasıyla yazdırılır, çünkü her goroutine yalnızca sizes'ın kendi indeksine yazar ve main onları yalnızca Wait'ten sonra okur.
Bir WaitGroup'un sıfır değeri kullanıma hazırdır. Kurucu yoktur.
Üç kural
Add'i goroutine'in içinde değil, go'dan önce çağırın. Goroutine Add'i kendisi çağırırsa main, herhangi bir goroutine başlamadan Wait'e ulaşabilir, sayıyı sıfır görebilir ve iş başlamamışken dönebilir. Sayıyı önceden bildiğinizde döngüden önce bir kez wg.Add(len(files)) çağırmak eşdeğerdir.
Done'u goroutine'in ilk satırında defer ile çağırın. Bir hatada erken dönen ya da panic olan bir goroutine yine de sayacı azaltır. Eksik bir Done, Wait'i sonsuza kadar bloklanmış bırakır. Geriye kalan tek goroutine oysa çalışma zamanı trace'te sync.WaitGroup.Wait ile birlikte fatal error: all goroutines are asleep raporlar.
Bir WaitGroup'u ilk kullanımdan sonra asla kopyalamayın. Fonksiyonlara *sync.WaitGroup aktarın ya da değişkeni yukarıdaki gibi bir closure içinde yakalayın.
Bir WaitGroup'u bir fonksiyona aktarmak
Goroutine gövdesi adlandırılmış bir fonksiyon olduğunda bir pointer aktarın:
Değer parametresi olarak wg sync.WaitGroup ile her worker kendi kopyası üzerinde Done çağırırdı ve main Wait içinde sonsuza kadar bloklanırdı. go vet bunu siz bir şey çalıştırmadan önce yakalar:
./main.go:8:24: worker passes lock by value: sync.WaitGroup contains sync.noCopy
Daha temiz bir tasarım eşzamanlılığı worker'ın tamamen dışında tutar: onu düz bir fonksiyon olarak bırakın ve Add/Done muhasebesini çağıranın closure'ında yapın. O zaman worker'ı test etmek ve senkron çağırmak kolaydır.
Negatif sayaç
Done, Add(-1)'dir. Sayı sıfırın altına düşerse program panic olur:
Çıktı recovered: sync: negative WaitGroup counter'dır. Olağan neden, defer wg.Done() içeren ve bir yolda wg.Done()'u ayrıca açıkça çağıran bir goroutine'dir.
Hataları toplamak
Bir WaitGroup yalnızca sayar. Hatalar için her goroutine'e kendi yuvasını verin ve Wait'ten sonra onları inceleyin:
errors.Join (Go 1.20) nil değerleri atlar ve hepsi nil ise nil döndürür, bu yüzden "goroutine başına bir hata"yı ek muhasebe olmadan birleştirir.
Bir goroutine başarısız olur olmaz kalan işi durdurmak istiyorsanız bunun yerine golang.org/x/sync/errgroup kullanın. Bir WaitGroup artı ilk hata artı başarısızlıkta iptal edilen bir context'tir ve g.SetLimit(n) eşzamanlılığı sınırlar. Standart kütüphanenin dışında yaşar, bu yüzden bu sayfanın editöründe çalışamaz:
g, ctx := errgroup.WithContext(ctx)
for _, h := range hosts {
g.Go(func() error { return checkCtx(ctx, h) })
}
if err := g.Wait(); err != nil {
return err // the first error; ctx was cancelled for the others
}
Bir worker havuzu
Bir channel'dan iş okuyan sabit sayıda goroutine, kaç iş olursa olsun eşzamanlılığı sınırlı tutar. WaitGroup tüm worker'ların ne zaman bittiğini söyler; bu da sonuç channel'ının kapatılabileceği andır.
Üç parçanın sırası önemlidir:
main, worker'lar çalışırken sonuçları alıyor olmalıdır.mainokumadan önce doğrudanwg.Wait()çağırsaydı worker'larresults'a gönderirken bloklanır,Done'a hiç ulaşmaz ve her şey deadlock'a girerdi.Wait'in kendi goroutine'inde çalışmasının nedeni budur.close(results)yalnızcaWait'ten sonra olur, böylece hiçbir worker kapatılmış bir channel'a gönderemez.- İş besleyici de bir goroutine'de çalışır, böylece besleme ve toplama örtüşür.
Hangi worker'ın hangi işi ele aldığı çalıştırmadan çalıştırmaya değişir, bu yüzden program yazdırmadan önce işe göre sıralar. Yazdırdığı her şey deterministiktir.
WaitGroup, channel ya da errgroup
| İhtiyaç | Kullanın |
|---|---|
| N goroutine'i beklemek, sonuçlar indeksli yuvalarda | sync.WaitGroup |
| Tek bir goroutine'i beklemek | bir done channel'ı ya da sonuç channel'ının kendisi |
| Bittikçe akan sonuçlar | wg.Wait()'ten sonra kapatılan bir channel |
| İlk hatada her şeyi durdurmak | errgroup.WithContext |
| Bir timeout ya da çağıran iptalinde her şeyi durdurmak | context.Context artı bir WaitGroup ya da errgroup |
Go 1.25, Add(1)'i ve ertelenmiş Done'u sizin yerinize yapan wg.Go(func() { ... })'yu ekliyor. Bu sayfanın editörü dahil Go 1.24 ve öncesi için kod, yukarıda gösterilen açık biçimi kullanır.
Sık yapılan hatalar
- Goroutine'in içinde
wg.Add(1).Waito çalışmadan dönebilir. - Erken bir dönüşte
Done'u unutmak. Her zamandefer wg.Done(). - WaitGroup'u değerle aktarmak. Bir pointer kullanın;
go vetkopyayı işaretler. - Bir channel'ı boşaltması gereken aynı goroutine'de beklemek.
wg.Wait()artıclose'u ayrı bir goroutine'e taşıyın. - Önceki
Waitdönmeden bir WaitGroup'u yeniden kullanmak. Yeni birAddçağrıları döngüsünü yalnızcaWaitbittikten sonra başlatın.
Sıkça Sorulan Sorular
Go'da sync.WaitGroup nasıl çalışır?
WaitGroup bir sayaçtır. wg.Add(n) onu artırır, wg.Done() bir azaltır ve wg.Wait() sıfıra ulaşana kadar bloklanır. Her goroutine'i başlatmadan önce Add'i, onun içinde defer wg.Done()'u ve her şeyin bitmesi gereken yerde Wait'i çağırın.
WaitGroup'u değerle mi yoksa pointer ile mi aktarmalıyım?
Pointer ile (*sync.WaitGroup) ya da goroutine'lerin onu bir closure içinde yakalamasına izin verin. Bir kopyanın kendi sayacı vardır, bu yüzden kopya üzerindeki Done asla orijinale ulaşmaz ve Wait sonsuza kadar bloklanır. go vet bu hatayı "passes lock by value" olarak raporlar.
"sync: negative WaitGroup counter" hatasına ne yol açar?
Add çağrılarından daha fazla Done çağrısı. Genellikle bir goroutine Done'u iki kez çağırır (bir kez defer ile, bir kez açıkça) ya da bir yolda Add(1) atlanır. Sayaç artık doğru hiçbir şey söyleyemediği için program panic olur.
WaitGroup ile başlatılan goroutine'lerden hatalar nasıl alınır?
Bir WaitGroup sonuç ya da hata taşımaz. Her goroutine'e bir hata slice'ında kendi yuvasını verin ve Wait'ten sonra onları birleştirin (örneğin errors.Join ile) ya da Wait'i ilk hatayı döndüren ve bir context üzerinden diğerlerini iptal edebilen golang.org/x/sync/errgroup'u kullanın.