sync.WaitGroup dapat digunakan kembali setelah sebuah wait phase selesai, tetapi task set independen baru tidak boleh dimulai selama call Wait dari phase sebelumnya masih aktif. Boundary-nya adalah return dari setiap Wait sebelumnya, bukan sekadar saat internal task counter mencapai nol.
Constraint ini penting ketika satu instance WaitGroup dipertahankan lintas batch, epoch, request wave, atau coordination cycle berulang. Reuse didukung, tetapi phase tidak boleh overlap pada transisi counter dari nol menjadi positif.
Nol adalah release point sekaligus phase boundary
WaitGroup mempertahankan task counter. Call Add positif menambah nilainya, Done ekuivalen dengan Add(-1), dan Wait memblokir sampai counter mencapai nol.
Ketika Done terakhir menurunkan counter menjadi nol, waiter yang terblokir dilepas. Waiter tersebut masih perlu kembali dari Wait. Memulai independent phase berikutnya sebelum seluruh return selesai melintasi reuse boundary.
Aturan ordering yang didokumentasikan bersifat spesifik: jika sebuah WaitGroup digunakan kembali untuk beberapa kumpulan event independen, call Add positif baru untuk set berikutnya harus terjadi setelah semua call Wait sebelumnya telah return.
Aturan ini lebih kuat daripada sekadar memeriksa bahwa worker sebelumnya sudah memanggil Done. Worker completion menyebabkan release, sedangkan waiter return menyelesaikan coordination phase.
Coordinator dapat membuat reuse menjadi eksplisit
Satu coordinator secara natural membuat boundary yang tidak overlap:
var wg sync.WaitGroup
for _, batch := range batches {
for _, job := range batch {
wg.Add(1)
go func(job Job) {
defer wg.Done()
process(job)
}(job)
}
wg.Wait()
}Setiap iteration menambahkan task saat counter masih nol dan sebelum Wait milik iteration tersebut. Iteration berikutnya tidak menjalankan Add(1) pertama sampai Wait sebelumnya telah return.
Object yang sama membawa beberapa task set, tetapi lifecycle-nya dipartisi menjadi phase berbeda. Reuse tidak membutuhkan reset WaitGroup; tidak ada reset operation. Counter yang kembali nol dan seluruh waiter yang return membentuk boundary yang dapat dipakai lagi.
Concurrent phase ownership menciptakan ordering hazard
Desain yang lebih rapuh membiarkan satu goroutine menunggu current phase sementara goroutine lain menentukan kapan phase berikutnya dimulai:
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
runCurrentPhase()
}()
go func() {
wg.Wait()
markPhaseComplete()
}()
// A separate control path must not begin an independent
// zero-to-positive transition until the earlier Wait has returned.Signal bahwa worker telah selesai tidak otomatis sama dengan bukti bahwa setiap waiter sudah return. Jika control path lain melakukan Add positif berikutnya hanya berdasarkan worker completion, phase ownership menjadi ambigu.
Ini terutama merupakan lifecycle ordering problem. Producer berikutnya membutuhkan synchronization event yang terikat pada completion dari prior wait phase, bukan observasi yang kebetulan berkorelasi dengan counter mencapai nol.
Positive Add memiliki ordering rule berbeda saat counter nol
Kontrak Add membedakan dua state. Add positif yang dimulai ketika counter lebih besar dari nol dapat terjadi saat pekerjaan masih aktif. Add positif yang dimulai ketika counter nol harus mendahului Wait yang bersesuaian.
Perbedaan ini mengizinkan dynamic task creation di dalam active phase. Sebagai contoh, existing task dapat menambahkan accounting untuk task lain saat group masih non-empty, selama program mempertahankan task accounting yang benar.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if needsExtraWork() {
wg.Add(1)
go func() {
defer wg.Done()
runExtraWork()
}()
}
}()
wg.Wait()Di sini initial positive count ditetapkan sebelum Wait. Positive accounting berikutnya dapat terjadi saat group sudah non-empty. Ini berbeda dari memulai independent phase baru dari nol ketika Wait sebelumnya masih in-flight.
WaitGroup.Go mengikuti empty-group boundary yang sama
Go modern juga menyediakan WaitGroup.Go, yang memulai task sekaligus melakukan accounting atas lifetime-nya sebagai satu operation. Ketika group kosong, Go harus terjadi sebelum Wait. Ketika group non-empty, Go dapat terjadi kapan saja, termasuk dari task yang sudah dilacak group.
Untuk task set independen yang berulang, call Go baru harus terjadi setelah seluruh call Wait sebelumnya return. Convenience method tersebut mengubah task-start bookkeeping, bukan phase boundary untuk reuse.
WaitGroup.Go juga memiliki kontrak terpisah bahwa function yang diberikan tidak boleh panic. Constraint itu independen dari reuse ordering.
Done membentuk memory-order edge ke waiter yang dilepas
Done memiliki synchronization guarantee di luar arithmetic counter. Call Done melakukan synchronize-before terhadap return dari call Wait yang dilepas olehnya.
Edge ini membuat Wait berguna sebagai completion barrier untuk state yang ditulis task yang dilacak. Setelah Wait relevan return, kode setelahnya dapat mengandalkan synchronization relationship yang didefinisikan kontrak WaitGroup.
Jaminan tersebut tidak mengubah WaitGroup menjadi general phase-generation primitive. Ia tidak memiliki generation identifier dan tidak mengekspos operation untuk me-reserve epoch berikutnya ketika waiter lama masih keluar. Program dengan producer dan consumer yang maju secara independen sering membutuhkan mutex, channel, condition, atau explicit generation state tambahan di sekitar phase transition.
Reuse hanya valid di antara generation yang tidak overlap
WaitGroup yang reusable merepresentasikan satu active task count pada satu waktu. Zero state bukan queue untuk future work dan tidak mengidentifikasi generation milik seorang waiter.
Penggunaan berulang yang aman karena itu bergantung pada external lifecycle structure: current generation mencapai nol, seluruh Wait miliknya return, lalu baru independent generation berikutnya membuat positive count baru. Menjaga ordering ini tetap eksplisit mempertahankan task accounting sekaligus synchronization boundary yang disediakan WaitGroup.