Golang Go语言中WaitGroup问题求指教
一直以为下面这段代码的输出结果会是 v1:25, v2:15 ,但是跑了几次发现结果出现了 20, 14; 23, 15; 20, 11 等等的随机结果,有点凌乱... 有大佬指出下是什么问题么
package main
import (
“fmt”
“sync”
)
func main() {
var wg sync.WaitGroup
intSlice := []int{1, 2, 3, 4, 5}
wg.Add(len(intSlice))
v1, v2 := 0, 0
for _, v := range intSlice {
vv := v
go func() {
defer wg.Done()
v1 += v
v2 += vv
}()
}
wg.Wait()
fmt.Printf("v1:%v, v2:%v \n", v1, v2)
}
Golang Go语言中WaitGroup问题求指教
更多关于Golang Go语言中WaitGroup问题求指教的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
WaitGroup 只是一个等待多个逻辑执行完成的同步工具, 他没有执行顺序性的功能.
你这里 v 是 for 循环里的变量, 在遍历过程中会变化, 协程执行时机不确定, 不能保证执行的时候 v 的值是多少.
go 所有地方都是值传递,但 closure 捕获变量是按引用捕获的。
https://go.dev/doc/faq#closures_and_goroutines
还有你 vv 变量的读写也存在 data race 。
而且你 v1, v2 在多个协程里直接加减, 都没一个锁来保护, 涉及到内存可见性的问题?
跟 wait group 没关系,是闭包的问题。那个匿名函数是闭包,又因为不是同步执行的,它执行的时候会访问到外面已经改变的值。为确保每个值都传递到得这样:
go func(v, vv int) {
…
}(v, vv)
v1 v2 加个通道吧
个人理解
for … range 的话,返回的 v 是个新建的一个地址,后续遍历的每个值都被赋在这个地址上, 你在 goroutine 里面用的话, 他是取的地址上的值 又因为你是起了等长数量的 goroutine, 执行的时候是无序的,在短时间片内该地址上的值是一样的,这就造成累加后的值不是 25 了
而 vv 变量, 在 vv:=v 的时候每次都会给 vv 重新创建了一个地址,无论 goroutine 怎么乱序读,slice 对应到的 vv 值都是不同地址的, 值也是不同的
-----
但有一点比较好奇,解决了地址的问题,为什么还是会出现这样的情况
func main() {
var wg sync.WaitGroup
intSlice := []int{1, 2, 3, 4, 5}
wg.Add(len(intSlice))
v1, v2 := 0, 0
for _, v := range intSlice {
vv := v
go func(v, vv int) {
defer wg.Done()
v1 += v
v2 += vv
}(v, vv)
}
wg.Wait()
fmt.Printf(“v1:%v, v2:%v \n”, v1, v2)
}
v1:15, v2:15
v1:15, v2:15
v1:15, v2:15
v1:10, v2:10
v1:13, v2:13
v1:15, v2:15
v1:15, v2:15
----
这个问题根本原因应该在于多个 goroutine 同时对 v1 和 v2 进行赋值导致的竞态问题,普通的赋值操作并不是一个原子操作。
可以看下这篇文章: https://cloud.tencent.com/developer/article/1489456
谢谢,学习了!
另外使用习惯上,如果 wg 会脱离当前的线程(协程)尽量传递引用(新开线程,传递,匿名函数),能避免很多潜在的问题
在Go语言中,sync.WaitGroup
是一个用于等待一组 goroutine 完成的计数器。它通常用于协调多个 goroutine 的执行,确保它们在主 goroutine 继续执行之前完成。
使用 WaitGroup
时,你需要执行以下步骤:
-
初始化:通过
var wg sync.WaitGroup
声明一个WaitGroup
变量。 -
增加计数器:在启动每个 goroutine 之前,使用
wg.Add(1)
增加计数器。计数器的值表示需要等待完成的 goroutine 数量。 -
在 goroutine 中调用 Done:在每个 goroutine 结束时,调用
wg.Done()
来减少计数器。这表示一个 goroutine 已经完成。 -
等待完成:在主 goroutine 中,调用
wg.Wait()
阻塞,直到计数器变为零,表示所有 goroutine 都已完成。
示例代码如下:
var wg sync.WaitGroup
func worker(id int) {
defer wg.Done() // 确保在函数结束时调用 Done
fmt.Printf("Worker %d starting\n", id)
// 模拟工作
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i)
}
wg.Wait() // 等待所有 worker 完成
fmt.Println("All workers done")
}
确保正确使用 defer wg.Done()
以避免忘记调用 Done
方法,从而导致 Wait
永远阻塞。希望这能帮助你理解 WaitGroup
的使用!