[英]How does Golang share variables between goroutines?
我正在学习 Go 并试图了解它的并发特性。
我有以下程序。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
x := i
go func() {
defer wg.Done()
fmt.Println(x)
}()
}
wg.Wait()
fmt.Println("Done")
}
执行时我得到:
4
0
1
3
2
这正是我想要的。 但是,如果我对其稍作修改:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
fmt.Println("Done")
}
我得到的是:
5
5
5
5
5
我不太明白其中的区别。 谁能帮忙解释一下这里发生了什么以及 Go 运行时如何执行这段代码?
每次运行x := i
都有新变量,
这段代码通过在 goroutine 中打印x
的地址很好地显示了差异:
围棋游乐场:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
x := i
go func() {
defer wg.Done()
fmt.Println(&x)
}()
}
wg.Wait()
fmt.Println("Done")
}
输出:
0xc0420301e0
0xc042030200
0xc0420301e8
0xc0420301f0
0xc0420301f8
Done
并使用go build -race
构建您的第二个示例并运行它:
您将看到: WARNING: DATA RACE
这会很好The Go Playground :
//go build -race
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
fmt.Println("Done")
}
输出:
0
4
1
2
3
Done
一般规则是,不要在 goroutine 之间共享数据。 在第一个示例中,您基本上为每个 goroutine 提供了自己的x
副本,然后它们以到达 print 语句的任何顺序将其打印出来。 在第二个示例中,它们都引用了相同的循环变量,并且在它们中的任何一个打印它时都会增加到 5。 我不相信那里的输出是有保证的,只是碰巧创建 goroutine 的循环完成得比 goroutine 本身到达打印部分的速度快。
用简单的英语解释有点困难,但我会尽力而为。
你看,每次你产生一个新的 goroutine 时,都会有一个初始化时间,不管它有多小,它总是存在的。 因此,在您的第二种情况下,在任何 goroutine 开始之前,整个循环已经完成了 5 次变量的递增。 当 goroutine 完成初始化时,它们看到的只是最终变量值 5。
但是,在第一种情况下, x 变量保留了 i 变量的副本,以便在 goroutine 启动时,将 x get 传递给它们。 请记住,这里增加的是i
,而不是x
。 x
是固定的。 因此,当 goroutine 启动时,它们会获得一个固定值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.