首先看一段程序:
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
//并发计算每个数的立方数
go func() {
fmt.Println(i * i * i)
}()
}
}
点击运行程序后,发现程序只解结束,没有输出任何内容:
这是因为main函数没有等待刚刚启动的协程结束就提前结束了,而main为主线程,主线程结束,则所有的子线程均被迫终止运行,所以我们需要等待所有的协程结束后才能结束主线程,这时可以使用sync.WaitGroup{} ,如下所示:
func main() {
w := &sync.WaitGroup{}
for i := 1; i <= 10; i++ {
w.Add(1)
//并发计算每个数的立方数
go func() {
defer w.Done()
fmt.Println(i * i * i)
}()
}
w.Wait()
}
终于有结果了,但是相当的诡异:
729
1331
1331
1331
1331
1331
1331
1331
1331
729
Process finished with exit code 0
这是什么鬼?
再运行一次瞅瞅:
1331
1331
1331
1331
1331
1331
1000
1331
1331
1000
Process finished with exit code 0
emmmmmmm…依然很奇幻。
首先,解释为啥会有结果了,这是因为通过waitGroup,相当于是给主线程加了一个wait锁,用于等待所有的协程运行结束,当所有的协程均运行结束时,wait锁才会被释放,程序才会继续运行。
其实,如果再去运行一次,结果可能又是另一个样子,这是因为协程共用了一个内存空间,而使用for range 得到的临时循环变量本质就是一个地址,当某个goroutine去使用该临时变量时,变量已经不是那次循环时的那个值了,已经换成了新的值。
闭包里引用了不作为参数传递进去的值,都是引用传递,也就是说,fmt.Println(i
i
i) 其实是引用了i的地址然后解引用,将值运算后才打印出来…等到这个goroutine执行fmt.Println(i
i
i) 的时候,i所指向的值已经不是当时希望的值了
所以,使用参数传递即可解决这个问题:
func main() {
w := &sync.WaitGroup{}
for i := 1; i <= 10; i++ {
w.Add(1)
//并发计算每个数的立方数
go func(x int) {
defer w.Done()
fmt.Println(x * x * x)
}(i)
}
w.Wait()
}
运行结果如下:
125
729
1000
1
512
8
216
27
64
343
Process finished with exit code 0
因为协程之间不存在依赖关系,所以输出的顺序并不是有序的。
最后引出一个可以简化上述编程方式的东西:wg.WaitGroupWrapper
可以省略
w.Add()
和
w.Done()
需要引入以下包:
"github.com/willas/golib/wg"
使用的方式如下:
package main
import (
"fmt"
"github.com/willas/golib/wg"
)
func main() {
w := &wg.WaitGroupWrapper{}
for i := 1; i <= 10; i++ {
//并发计算每个数的立方数
w.Wrap(func() {
func(x int) {
fmt.Println(x * x * x)
}(i)
})
}
w.Wait()
}
但结果又。。。:
1331
1331
1331
1331
1331
1331
1331
1331
1331
1331
Process finished with exit code 0
为啥呢?其实我们已经知道咯,是因为闭包引用作用域内的非参数传入的变量导致的,i不是通过参数传入的,所以使用的是其地址并解引用。又因为Wrap的参数为func(),无法使用之前的方式传入参数,所以可以通过定义新变量的方式解决:
func main() {
w := &wg.WaitGroupWrapper{}
for i := 1; i <= 10; i++ {
x := i
//并发计算每个数的立方数
w.Wrap(func() {
fmt.Println(x * x * x)
})
}
w.Wait()
}
这样结果就ok啦:
1
8
343
27
216
1000
729
512
125
64
Process finished with exit code 0