golang并发编程——wg.WaitGroupWrapper

本文探讨了在Go语言中使用协程并发计算时遇到的问题,包括如何正确使用sync.WaitGroup来确保所有协程完成,以及如何避免因闭包引用问题导致的数据错误。通过实例演示了如何传递参数到协程,确保每个协程拥有独立的变量副本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看一段程序:

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(iii) 其实是引用了i的地址然后解引用,将值运算后才打印出来…等到这个goroutine执行fmt.Println(iii) 的时候,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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值