Concurrency Basics
Wait Group
A sync.WaitGroup lets main wait until a goroutine calls Done.
wait group
A wait group counts active tasks so code can wait for them to finish before reading final results.
Wait Group
wait_group.go
Replay: real traced execution (multi-file project)
package main
import (
"fmt"
"sync"
)
func main() {
var base = 4
var wg sync.WaitGroup
results := make(chan int, 1)
wg.Add(1)
go func() {
defer wg.Done()
results <- base * 2
}()
wg.Wait()
value := <-results
fmt.Println("base=", base)
fmt.Println("value=", value)
fmt.Println("complete=", true)
}
package main
import (
"fmt"
"sync"
)
func main() {
var base = 2
var wg sync.WaitGroup
results := make(chan int, 1)
wg.Add(1)
go func() {
defer wg.Done()
results <- base * 2
}()
wg.Wait()
value := <-results
fmt.Println("base=", base)
fmt.Println("value=", value)
fmt.Println("complete=", true)
}
package main
import (
"fmt"
"sync"
)
func main() {
var base = 6
var wg sync.WaitGroup
results := make(chan int, 1)
wg.Add(1)
go func() {
defer wg.Done()
results <- base * 2
}()
wg.Wait()
value := <-results
fmt.Println("base=", base)
fmt.Println("value=", value)
fmt.Println("complete=", true)
}
base ← 4, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
8func main() {9 var base→ 4 = 4 //@base=2, 610 var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11 results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14 go func() {15 defer wg.Done()16 results <- base * 217 }()1819 wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20 value := <-resultsfunc()
13wg.Add(1)14go func() {15 defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16 results(chan int)(⟨addr A⟩) <- base4 * 217}()wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
19 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20 value→ 8 := <-results(chan int)(⟨addr A⟩)21 fmt.Println("base=", base4)22 fmt.Println("value=", value8)23 fmt.Println("complete=", true)24}outputbase= 4 value= 8 complete= true
base ← 2, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
8func main() {9 var base→ 2 = 210 var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11 results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14 go func() {15 defer wg.Done()16 results <- base * 217 }()1819 wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20 value := <-resultsfunc()
13wg.Add(1)14go func() {15 defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16 results(chan int)(⟨addr A⟩) <- base2 * 217}()wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
19 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20 value→ 4 := <-results(chan int)(⟨addr A⟩)21 fmt.Println("base=", base2)22 fmt.Println("value=", value4)23 fmt.Println("complete=", true)24}outputbase= 2 value= 4 complete= true
base ← 6, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
8func main() {9 var base→ 6 = 610 var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11 results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14 go func() {15 defer wg.Done()16 results <- base * 217 }()1819 wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20 value := <-resultsfunc()
13wg.Add(1)14go func() {15 defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16 results(chan int)(⟨addr A⟩) <- base6 * 217}()wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}
19 wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20 value→ 12 := <-results(chan int)(⟨addr A⟩)21 fmt.Println("base=", base6)22 fmt.Println("value=", value12)23 fmt.Println("complete=", true)24}outputbase= 6 value= 12 complete= true
Follow the Wait
basestarts at4.wg.Add(1)records one goroutine to wait for.- The goroutine sends
base * 2, so it sends8intoresults. defer wg.Done()marks the goroutine finished.wg.Wait()returns, then main reads8and printscomplete= true. | base | goroutine sends | main reads | final lines | | ---: | ---: | ---: | --- | | 2 | 4 | 4 | base= 2; value= 4; complete= true | | 4 | 8 | 8 | base= 4; value= 8; complete= true | | 6 | 12 | 12 | base= 6; value= 12; complete= true |
Exercise: wait_group.go
Reproduce base= 4, value= 8, and complete= true, then use base 2 and 6 to predict value= 4 and value= 12.