官网说明
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls
Add
to set the number of goroutines to wait for. Then each of the goroutines runs and callsDone
when finished. At the same time,Wait
can be used to block until all goroutines have finished.
WaitGroup
同步的是 go
协程
示例
示例一
package main
import (
"fmt"
"sync"
)
func main(){
wg := sync.WaitGroup{}
for i := 0; i < 8; i++ {
go func(i int){
wg.Add(1)
defer wg.Done()
fmt.Println("the %d goroutine\n",i)
}(i)
}
wg.Wait()
fmt.Println("main exit...")
}
在执行上面的代码后,会出现以下情况:
情况一:
# go run mywait.go
the 7 goroutine
the 6 goroutine
the 0 goroutine
the 2 goroutine
the 3 goroutine
the 1 goroutine
the 4 goroutine
the 5 goroutine
main exit...
情况二:
# go run mywait.go
main exit...
情况三:
# go run mywait.go
the 1 goroutine
the 4 goroutine
the 7 goroutine
the 6 goroutine
the 3 goroutine
the 2 goroutine
the 0 goroutine
the 5 goroutine
panic: sync: WaitGroup is reused before previous Wait has returned
goroutine 1 [running]:
sync.(*WaitGroup).Wait(0xc000016090)
/usr/local/go/src/sync/waitgroup.go:132 +0xad
main.main()
/Users/goproj/src/person/mywait.go:17 +0x83
exit status 2
根据开头的英文解释,WaitGroup
是同步 goroutine
的。在 go
协程内调用 wg.Add(1)
并不一定会被 main
流程感知。会出现 go
协和还未被调度,主协程已经运行到 wg.Wait()
而直接终止,或像情况三那样,在调用 wg.Wait()
之后,还未执行打印,就又调度 go
协程,在协程中执行 wg.Add(1)
时,而此时 Wait()
已经返回了,就会抛出一个 panic
。
示例二
再来看下面的例子
package main
import (
"fmt"
"sync"
)
func main(){
wg := sync.WaitGroup{}
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int,wg sync.WaitGroup){
defer wg.Done()
fmt.Println("the %d goroutine\n",i)
}(i,wg)
}
wg.Wait()
fmt.Println("main exit...")
}
打印如下:
go run mywait.go
the 7 goroutine
the 5 goroutine
the 6 goroutine
the 2 goroutine
the 3 goroutine
the 0 goroutine
the 1 goroutine
the 4 goroutine
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000016098)
/usr/local/go/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0xc000016090)
/usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
/Users/goproj/src/person/mywait.go:17 +0xab
exit status 2
此时传递给 go
协程的是 wg 的副本,调用 defer wg.Done()
和 wg.Add(1)
并不是同一个 wg
。
解决方法是将传值改为传指针。
示例三 值传递 and 指针传递
值传递传递的是变量的副本 ,指针传递传递的就是变量本身吗?
请看示例
package main
import (
"fmt"
"sync"
)
func main(){
wg := &sync.WaitGroup{}
for i := 0; i < 2; i++ {
fmt.Printf("out wg memory address:%p,wg pointer address:%p\n",wg,&wg)
fmt.Printf("out i:%p,*i:%p\n",i,&i)
wg.Add(1)
go func(wg *sync.WaitGroup,i int){
defer wg.Done()
fmt.Printf("inner wg memory address:%p,wg pointer address:%p\n",wg,&wg)
fmt.Printf("inner i:%p,*i:%p\n",i,&i)
}(wg,i)
}
wg.Wait()
fmt.Printf("main exit...\n")
}
打印如下:
# go run mywait.go
out wg memory address:0xc000016090,wg pointer address:0xc00000c028
out i:%!p(int=0),*i:0xc0000160a0
out wg memory address:0xc000016090,wg pointer address:0xc00000c028
out i:%!p(int=1),*i:0xc0000160a0
inner wg memory address:0xc000016090,wg pointer address:0xc00000c038
inner i:%!p(int=1),*i:0xc0000160b0
inner wg memory address:0xc000016090,wg pointer address:0xc00008a000
inner i:%!p(int=0),*i:0xc00008c000
从上面的打印可以看出,go
语言中指针传递,传递的仍是变量的副本 ,只不过变量是指针,传递的是指针的副本,但指针指向的内容是同一块内存的内容。
参考: