Pola dasarnya
sync.WaitGroup menghitung goroutine yang sedang berjalan. Add menaikkan hitungannya, Done menurunkannya, Wait memblokir sampai hitungannya nol.
Tiga download itu berjalan bersamaan, jadi program butuh sekitar 10 ms, bukan 30. Hasilnya dicetak sesuai urutan input karena setiap goroutine hanya menulis ke indeksnya sendiri di sizes, dan main baru membacanya setelah Wait.
Zero value dari WaitGroup sudah siap dipakai. Tidak perlu constructor.
Tiga aturan
Panggil Add sebelum go, bukan di dalam goroutine. Kalau goroutine memanggil Add sendiri, main bisa sampai di Wait sebelum ada goroutine yang mulai, melihat hitungan nol, lalu return padahal pekerjaannya belum dimulai. Kalau kamu sudah tahu jumlahnya sejak awal, wg.Add(len(files)) sekali sebelum perulangan hasilnya sama.
Panggil Done dengan defer di baris pertama goroutine. Goroutine yang return lebih awal karena error, atau panic, tetap menurunkan penghitungnya. Done yang terlupa membuat Wait memblokir selamanya. Kalau itu satu-satunya goroutine yang tersisa, runtime melaporkan fatal error: all goroutines are asleep dengan sync.WaitGroup.Wait di trace-nya.
Jangan pernah menyalin WaitGroup setelah pertama kali dipakai. Kirim *sync.WaitGroup ke fungsi, atau tangkap variabelnya lewat closure seperti di atas.
Mengirim WaitGroup ke fungsi
Kalau badan goroutine berupa fungsi bernama, kirim pointer-nya:
Dengan wg sync.WaitGroup sebagai parameter nilai, setiap worker akan memanggil Done pada salinannya sendiri dan main akan memblokir di Wait selamanya. go vet menangkapnya sebelum kamu menjalankan apa pun:
./main.go:8:24: worker passes lock by value: sync.WaitGroup contains sync.noCopy
Desain yang lebih rapi menjauhkan konkurensi dari worker sepenuhnya: biarkan ia menjadi fungsi biasa, dan lakukan pencatatan Add/Done di closure milik pemanggil. Dengan begitu worker mudah diuji dan dipanggil secara sinkron.
Penghitung negatif
Done sama dengan Add(-1). Kalau hitungannya turun di bawah nol, program akan panic:
Output-nya adalah recovered: sync: negative WaitGroup counter. Penyebab yang biasa adalah goroutine dengan defer wg.Done() yang juga memanggil wg.Done() secara eksplisit di salah satu jalurnya.
Mengumpulkan error
WaitGroup hanya menghitung. Untuk error, beri setiap goroutine slotnya sendiri dan periksa setelah Wait:
errors.Join (Go 1.20) melewati nilai nil dan mengembalikan nil jika semuanya nil, jadi fungsi ini menggabungkan "satu error per goroutine" tanpa pencatatan tambahan.
Kalau kamu ingin menghentikan pekerjaan yang tersisa begitu ada satu goroutine yang gagal, pakai golang.org/x/sync/errgroup. Ini adalah WaitGroup ditambah error pertama ditambah context yang dibatalkan saat terjadi kegagalan, dan g.SetLimit(n) membatasi konkurensinya. Package ini berada di luar pustaka standar, jadi tidak bisa dijalankan di editor halaman ini:
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
}
Worker pool
Sejumlah goroutine tetap yang membaca job dari sebuah channel menjaga konkurensi tetap terbatas, tidak peduli berapa banyak job-nya. WaitGroup memberi tahu kapan semua worker selesai, dan saat itulah channel hasil boleh ditutup.
Urutan ketiga bagian ini penting:
mainharus sedang menerima hasil selama worker berjalan. Kalaumainlangsung memanggilwg.Wait()sebelum membaca, worker akan terblokir saat mengirim keresults, tidak pernah sampai keDone, dan semuanya mengalami deadlock. Itulah sebabnyaWaitdijalankan di goroutine-nya sendiri.close(results)hanya terjadi setelahWait, jadi tidak ada worker yang bisa mengirim ke channel yang sudah ditutup.- Pengirim job juga berjalan di sebuah goroutine, jadi proses mengirim dan mengumpulkan berlangsung bersamaan.
Worker mana yang menangani job mana berubah di setiap run, jadi program mengurutkan berdasarkan job sebelum mencetak. Semua yang dicetaknya deterministik.
WaitGroup, channel, atau errgroup
| Kebutuhan | Pakai |
|---|---|
| Menunggu N goroutine, hasil di slot berindeks | sync.WaitGroup |
| Menunggu satu goroutine | channel done atau channel hasil itu sendiri |
| Hasil dialirkan begitu selesai | channel, ditutup setelah wg.Wait() |
| Menghentikan semuanya pada error pertama | errgroup.WithContext |
| Menghentikan semuanya karena timeout atau dibatalkan pemanggil | context.Context ditambah WaitGroup atau errgroup |
Go 1.25 menambahkan wg.Go(func() { ... }), yang melakukan Add(1) dan Done yang di-defer untukmu. Kode untuk Go 1.24 dan sebelumnya, termasuk editor di halaman ini, memakai bentuk eksplisit seperti di atas.
Kesalahan umum
wg.Add(1)di dalam goroutine.Waitbisa return sebelum baris itu dijalankan.- Lupa
Donesaat return lebih awal. Selalu tulisdefer wg.Done(). - Mengirim WaitGroup sebagai nilai. Pakai pointer;
go vetmenandai penyalinannya. - Menunggu di goroutine yang sama yang harus mengosongkan channel. Pindahkan
wg.Wait()besertacloseke goroutine terpisah. - Memakai ulang WaitGroup sebelum
Waitsebelumnya return. Mulai siklus panggilanAddyang baru hanya setelahWaitselesai.
Pertanyaan yang sering diajukan
Bagaimana cara kerja sync.WaitGroup di Go?
WaitGroup adalah sebuah penghitung. wg.Add(n) menaikkannya, wg.Done() menurunkannya satu, dan wg.Wait() memblokir sampai nilainya nol. Panggil Add sebelum menjalankan setiap goroutine, defer wg.Done() di dalamnya, dan Wait di tempat kamu butuh semuanya sudah selesai.
Sebaiknya WaitGroup dikirim sebagai nilai atau pointer?
Sebagai pointer (*sync.WaitGroup), atau biarkan goroutine menangkapnya lewat closure. Salinan punya penghitungnya sendiri, jadi Done pada salinan tidak pernah sampai ke aslinya dan Wait memblokir selamanya. go vet melaporkan kesalahan ini sebagai "passes lock by value".
Apa penyebab "sync: negative WaitGroup counter"?
Panggilan Done lebih banyak daripada panggilan Add. Biasanya sebuah goroutine memanggil Done dua kali (sekali dengan defer dan sekali secara eksplisit), atau Add(1) terlewat di salah satu jalur. Program akan panic, karena penghitung itu tidak lagi bisa memberi informasi yang benar.
Bagaimana cara mendapatkan error dari goroutine yang dijalankan dengan WaitGroup?
WaitGroup tidak membawa hasil atau error. Beri setiap goroutine slotnya sendiri di sebuah slice error dan gabungkan setelah Wait (misalnya dengan errors.Join), atau pakai golang.org/x/sync/errgroup, yang Wait-nya mengembalikan error pertama dan bisa membatalkan goroutine lain lewat sebuah context.