WaitGroup与defer
WaitGroup
WaitGroup
是一个结构体类型,用于等待一批Go协程执行结束。程序会一直阻塞,直到这些协程全部执行完毕。主线程(goroutine)才会执行
WaitGroup 的使用
通过前面信道一文,我们可以利用信道实现和waitgroup
一样的效果。代码如下:
func main() {
fmt.Println("程序开始")
c := make(chan int, 5)
for i := 0; i < 5; i++ {
go Test(i, c)
}
for i:=0; i<cap(c);i++{ // 这里不能用len,原因在于,主goroutine执行到这一步时,其他的goroutine未必执行完了,因此 信道 c 中的长度不等于5.会报deadlock错误
<- c
}
fmt.Println("程序结束")
}
func Test(s int, c chan int) {
fmt.Println(s)
c <- s
}
`
程序开始
4
0
1
2
3
程序结束
`
可以看出,等待5个子goroutine
执行,代码就有一丢丢复杂了,如果要等待更多,代码量虽然不会增加,但是 信道的容量确需要 变化。接下来我们看看使用 waitgroup
实现的代码。
func main() {
fmt.Println("程序开始")
// WaitGroup 是 结构体, 值类型,不用初始化就有默认零值;当参数传递,如果改原来的,需要取地址
var wg = sync.WaitGroup{}
for i := 0; i < 5; i++ {
go Test2(i, &wg)
wg.Add(1) // 每循环一次就启动一个任务
}
//wg.Add(5) // 也可以一次性启动5个
wg.Wait() // 等待所有任务完成
fmt.Println("程序结束")
}
func Test2(s int, wg *sync.WaitGroup) {
fmt.Println(s)
wg.Done() // 结束一个任务
}
可以看出,使用waitgroup
,代码就简单很多了,多一个子goroutine,只需要多启动一个任务即可。
defer
defer
语句的用途是:含有 defer
语句的函数,会在该函数将要返回之前,调用另一个函数。这个定义可能看起来很复杂,我们通过一个示例就很容易明白了。
func main() {
Test3()
}
func Test3() {
defer finally() // 先注册,后执行 哪怕下方代码出错也会执行
// finally()
fmt.Println("Test3: 我开始执行了")
fmt.Println("Test3: 我执行结束了")
}
func finally() {
fmt.Println("到我执行了")
}
`
使用defer打印结果:
Test3: 我开始执行了
Test3: 我执行结束了
到我执行了
`
`
不使用defer打印结果:
到我执行了
Test3: 我开始执行了
Test3: 我执行结束了
`
简单点说就是: 注册代码,延迟调用,等函数执行完后,按注册顺序倒序执行defer,先注册的,最后执行。
并且defer,不仅可以用于函数,也可以用于方法的调用。感兴趣的小伙伴可以自己试试。
实参取值
在 Go 语言中,并非在调用延迟函数的时候才确定实参,而是当执行 defer
语句的时候,就会对延迟函数的实参进行求值。
通过一段例子,就能很好理解了
func main() {
i := 4
defer finally(i) // 定义的时候就已经把参数传入了,只是还没有执行
defer finally(&i)
i++
}
func finally(i int) {
fmt.Println(i) // 4
}
func finally2(i *int) {
fmt.Println(*i) // 5
}
`
5
4
`
因此,值类型数据使用 defer
函数传参后,值改变不影响 defer后函数的参数。但是引用类型会影响。
defer 栈
当一个函数内多次调用 defer
时,Go 会把 defer
调用放入到一个栈中,随后按照后进先出(Last In First Out, LIFO)的顺序执行。代码示例可以看看上方。