Source File
waitgroup.go
Belonging Package
sync
// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package syncimport ()// A WaitGroup is a counting semaphore typically used to wait// for a group of goroutines or tasks to finish.//// Typically, a main goroutine will start tasks, each in a new// goroutine, by calling [WaitGroup.Go] and then wait for all tasks to// complete by calling [WaitGroup.Wait]. For example://// var wg sync.WaitGroup// wg.Go(task1)// wg.Go(task2)// wg.Wait()//// A WaitGroup may also be used for tracking tasks without using Go to// start new goroutines by using [WaitGroup.Add] and [WaitGroup.Done].//// The previous example can be rewritten using explicitly created// goroutines along with Add and Done://// var wg sync.WaitGroup// wg.Add(1)// go func() {// defer wg.Done()// task1()// }()// wg.Add(1)// go func() {// defer wg.Done()// task2()// }()// wg.Wait()//// This pattern is common in code that predates [WaitGroup.Go].//// A WaitGroup must not be copied after first use.type WaitGroup struct {noCopy noCopy// Bits (high to low):// bits[0:32] counter// bits[32] flag: synctest bubble membership// bits[33:64] wait countstate atomic.Uint64sema uint32}// waitGroupBubbleFlag indicates that a WaitGroup is associated with a synctest bubble.const waitGroupBubbleFlag = 0x8000_0000// Add adds delta, which may be negative, to the [WaitGroup] task counter.// If the counter becomes zero, all goroutines blocked on [WaitGroup.Wait] are released.// If the counter goes negative, Add panics.//// Callers should prefer [WaitGroup.Go].//// Note that calls with a positive delta that occur when the counter is zero// must happen before a Wait. Calls with a negative delta, or calls with a// positive delta that start when the counter is greater than zero, may happen// at any time.// Typically this means the calls to Add should execute before the statement// creating the goroutine or other event to be waited for.// If a WaitGroup is reused to wait for several independent sets of events,// new Add calls must happen after all previous Wait calls have returned.// See the WaitGroup example.func ( *WaitGroup) ( int) {if race.Enabled {if < 0 {// Synchronize decrements with Wait.race.ReleaseMerge(unsafe.Pointer())}race.Disable()defer race.Enable()}:= falseif synctest.IsInBubble() {// If Add is called from within a bubble, then all Add calls must be made// from the same bubble.switch synctest.Associate() {case synctest.Unbubbled:case synctest.OtherBubble:// wg is already associated with a different bubble.fatal("sync: WaitGroup.Add called from multiple synctest bubbles")case synctest.CurrentBubble:= true:= .state.Or(waitGroupBubbleFlag)if != 0 && &waitGroupBubbleFlag == 0 {// Add has been called from outside this bubble.fatal("sync: WaitGroup.Add called from inside and outside synctest bubble")}}}:= .state.Add(uint64() << 32)if &waitGroupBubbleFlag != 0 && ! {// Add has been called from within a synctest bubble (and we aren't in one).fatal("sync: WaitGroup.Add called from inside and outside synctest bubble")}:= int32( >> 32):= uint32( & 0x7fffffff)if race.Enabled && > 0 && == int32() {// The first increment must be synchronized with Wait.// Need to model this as a read, because there can be// several concurrent wg.counter transitions from 0.race.Read(unsafe.Pointer(&.sema))}if < 0 {panic("sync: negative WaitGroup counter")}if != 0 && > 0 && == int32() {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}if > 0 || == 0 {return}// This goroutine has set counter to 0 when waiters > 0.// Now there can't be concurrent mutations of state:// - Adds must not happen concurrently with Wait,// - Wait does not increment waiters if it sees counter == 0.// Still do a cheap sanity check to detect WaitGroup misuse.if .state.Load() != {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// Reset waiters count to 0..state.Store(0)if {// Adds must not happen concurrently with wait when counter is 0,// so we can safely disassociate wg from its current bubble.synctest.Disassociate()}for ; != 0; -- {runtime_Semrelease(&.sema, false, 0)}}// Done decrements the [WaitGroup] task counter by one.// It is equivalent to Add(-1).//// Callers should prefer [WaitGroup.Go].//// In the terminology of [the Go memory model], a call to Done// "synchronizes before" the return of any Wait call that it unblocks.//// [the Go memory model]: https://go.dev/ref/memfunc ( *WaitGroup) () {.Add(-1)}// Wait blocks until the [WaitGroup] task counter is zero.func ( *WaitGroup) () {if race.Enabled {race.Disable()}for {:= .state.Load():= int32( >> 32):= uint32( & 0x7fffffff)if == 0 {// Counter is 0, no need to wait.if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer())}if == 0 && &waitGroupBubbleFlag != 0 && synctest.IsAssociated() {// Adds must not happen concurrently with wait when counter is 0,// so we can disassociate wg from its current bubble.if .state.CompareAndSwap(, 0) {synctest.Disassociate()}}return}// Increment waiters count.if .state.CompareAndSwap(, +1) {if race.Enabled && == 0 {// Wait must be synchronized with the first Add.// Need to model this is as a write to race with the read in Add.// As a consequence, can do the write only for the first waiter,// otherwise concurrent Waits will race with each other.race.Write(unsafe.Pointer(&.sema))}:= falseif &waitGroupBubbleFlag != 0 && synctest.IsInBubble() {if race.Enabled {race.Enable()}if synctest.IsAssociated() {// Add was called within the current bubble,// so this Wait is durably blocking.= true}if race.Enabled {race.Disable()}}runtime_SemacquireWaitGroup(&.sema, )if .state.Load() != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer())}return}}}// Go calls f in a new goroutine and adds that task to the [WaitGroup].// When f returns, the task is removed from the WaitGroup.//// The function f must not panic.//// If the WaitGroup is empty, Go must happen before a [WaitGroup.Wait].// Typically, this simply means Go is called to start tasks before Wait is called.// If the WaitGroup is not empty, Go may happen at any time.// This means a goroutine started by Go may itself call Go.// If a WaitGroup is reused to wait for several independent sets of tasks,// new Go calls must happen after all previous Wait calls have returned.//// In the terminology of [the Go memory model], the return from f// "synchronizes before" the return of any Wait call that it unblocks.//// [the Go memory model]: https://go.dev/ref/memfunc ( *WaitGroup) ( func()) {.Add(1)go func() {defer .Done()()}()}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |