golang并发编程——wg.WaitGroupWrapper

  • Post author:
  • Post category:golang


首先看一段程序:

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



版权声明:本文为wxs19970115原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。