Menu

Golang WaitGroup: Add, Done, Wait ve bir Worker Havuzu

sync.WaitGroup bir grup goroutine'in bitmesini nasıl bekler: Add, Done ve Wait kuralları, neden pointer ile aktarılması gerektiği, sonuçları ve hataları toplamak ve onun üzerine kurulmuş bir worker havuzu.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

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. main okumadan önce doğrudan wg.Wait() çağırsaydı worker'lar results'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ızca Wait'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 yuvalardasync.WaitGroup
Tek bir goroutine'i beklemekbir done channel'ı ya da sonuç channel'ının kendisi
Bittikçe akan sonuçlarwg.Wait()'ten sonra kapatılan bir channel
İlk hatada her şeyi durdurmakerrgroup.WithContext
Bir timeout ya da çağıran iptalinde her şeyi durdurmakcontext.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). Wait o çalışmadan dönebilir.
  • Erken bir dönüşte Done'u unutmak. Her zaman defer wg.Done().
  • WaitGroup'u değerle aktarmak. Bir pointer kullanın; go vet kopyayı işaretler.
  • Bir channel'ı boşaltması gereken aynı goroutine'de beklemek. wg.Wait() artı close'u ayrı bir goroutine'e taşıyın.
  • Önceki Wait dönmeden bir WaitGroup'u yeniden kullanmak. Yeni bir Add çağrıları döngüsünü yalnızca Wait bittikten 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.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA