刚开始用 go, 习惯 Java 了, 不小心踩了一个坑.
本意是 print 0 到 9 的. 虽然通过加参数解决了, 但是为啥会这样呢?
fmt.Printf("go i=%d\n", i)
这里面 i 的值是怎么获得的, 和 for 共享吗?
请指教下, 多谢了
for i := 0; i < 10; i++ {
go func() {
fmt.Printf("go i=%d\n", i)
}()
}
1
skadi 2019-07-05 12:02:08 +08:00
go func(num int){
// print after create }(i) |
2
ruin2016 2019-07-05 12:12:07 +08:00
package main
import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { fmt.Printf("go i=%d\n", i) wg.Done() }(i) } wg.Wait() } go i=9 go i=5 go i=6 go i=7 go i=8 go i=2 go i=3 go i=0 go i=1 go i=4 |
3
ooToo OP |
4
fuxiaohei 2019-07-05 12:29:43 +08:00 1
go 的命令可以理解为生成 goroutine 包含一个上下文,把 i 引入了上下文中。当 goroutine 需要运行时,才会调用上下文中的 i 的值。此时可能 i 已经变了。创建 goroutine 到运行 goroutine 总会有时间差的,显然 for 循环一般比调度协程要快得多。
|
5
impl 2019-07-05 12:30:31 +08:00 via Android
另一种写法
for i := 0; i < 10; i++ { i := i go func() { fmt.Printf("go i=%d\n", i) }() } |
6
iceheart 2019-07-05 12:43:16 +08:00 via Android 1
c++可以指定捕获方式是传值还是引用,其他多数语言闭包捕获都是只传引用。
|
7
BruceAuyeung 2019-07-05 12:50:40 +08:00 via Android 2
go 创建协程时,只求了方法入参的值,方法体里面的变量引用在代码执行到那里时才运算
for 循环中的 i 是多次迭代共享的,每次迭代会覆盖旧直值 所以当协程实际跑到访问 i 变量时,都不知道迭代到哪个地方了,值是不确定的 |
8
webee 2019-07-05 13:08:01 +08:00 1
i 只初始化一次,作用域是 for 这个 block.
且 go routine 在大多数情况下遇到阻塞时都会放弃执行,所以 for loop 结束时,那些新起的 go routine 才开始被调度。 这种情况下可以通过加参数或者在 for loop block 中新建变量来解决,这叫捕获循环变量。 在使用 ide 的时候,go vet 会对这种情况有提示。 |
9
ScepterZ 2019-07-05 13:08:04 +08:00
输出运行到 print 时候的值,一般因为循环的快,go 的慢,会出来全是 9
|
10
gamexg 2019-07-05 13:17:44 +08:00 1
上面说的比较清楚了,go 关键字起的函数并不能保证立刻启动,大概率是 for 结束后才启动,这样造成打印时不能保证 i 的值是什么了。
解决办法有给函数加参数,另外也可以这么写。 for i := 0; i < 10; i++ { i:=i go func() { fmt.Printf("go i=%d\n", i) }() } |
11
reus 2019-07-05 13:31:51 +08:00
i 是同一个变量,所有 goroutine 用到的都是同一个变量,所以你的程序是错的
需要在 go 语句前加一句 i := i,创建一个独立的变量。 |