Golang 如何在 goroutine 之间共享变量?
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 运行时如何执行这段代码?
一般规则是,不要在 goroutine 之间共享数据。在第一个示例中,您基本上为每个 goroutine 提供了自己的 x
副本,并且它们以到达 print 语句的任何顺序打印出来。在第二个例子中,它们都引用同一个循环变量,并且在它们中的任何一个打印它时递增到 5。我不相信那里的输出是有保证的,只是碰巧循环创建 goroutines 的完成速度比 goroutines 本身到达打印部分的速度快。
您在 x := i
、
的每个 运行 上都有新变量
这段代码很好地显示了差异,通过在 goroutine 中打印 x
的地址:
The Go Playground:
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 时,都会有一个初始化时间,无论它多么微小,它总是在那里。所以,在你的第二种情况下,整个循环在任何 goroutines 开始之前就完成了变量的递增 5 次。当 goroutines 完成初始化时,他们看到的只是最终变量值 5.
不过,在您的第一种情况下,x 变量保留了 i 变量的副本,这样当 goroutine 启动时,x get 就会传递给它们。请记住,这里递增的是 i
,而不是 x
。 x
已修复。因此,当 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 运行时如何执行这段代码?
一般规则是,不要在 goroutine 之间共享数据。在第一个示例中,您基本上为每个 goroutine 提供了自己的 x
副本,并且它们以到达 print 语句的任何顺序打印出来。在第二个例子中,它们都引用同一个循环变量,并且在它们中的任何一个打印它时递增到 5。我不相信那里的输出是有保证的,只是碰巧循环创建 goroutines 的完成速度比 goroutines 本身到达打印部分的速度快。
您在 x := i
、
的每个 运行 上都有新变量
这段代码很好地显示了差异,通过在 goroutine 中打印 x
的地址:
The Go Playground:
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 时,都会有一个初始化时间,无论它多么微小,它总是在那里。所以,在你的第二种情况下,整个循环在任何 goroutines 开始之前就完成了变量的递增 5 次。当 goroutines 完成初始化时,他们看到的只是最终变量值 5.
不过,在您的第一种情况下,x 变量保留了 i 变量的副本,这样当 goroutine 启动时,x get 就会传递给它们。请记住,这里递增的是 i
,而不是 x
。 x
已修复。因此,当 goroutines 启动时,它们会得到一个固定值。