在Go语言中,如果不将变量复制一份,闭包在Go协程中的作用域会出现错误。

huangapple go评论90阅读模式
英文:

Closures in go routines have incorrect scope unless the variables are copied

问题

我在作为goroutine运行的函数中看到了错误的值。除非将这些值复制到新的变量中,否则它们似乎无法捕获调用它们的作用域中的值。

http://play.golang.org/p/YZYi-IVuYm

http://play.golang.org/p/z88G99XSi6

英文:

I am seeing incorrect values in the functions run as goroutines. They don't seem to be capturing the values from the scope in which they were invoked unless copied into new variables.

http://play.golang.org/p/YZYi-IVuYm

vs.

http://play.golang.org/p/z88G99XSi6

答案1

得分: 14

你可以通过两种方式解决这个问题:

  1. 在本地上下文中重新分配变量,以便闭包可以捕获值:
package main

import "fmt"
import "time"

func main() {
    l := []int{1, 2, 3}
    for idx, item := range l {
        theIdx, theItem := idx, item
        go func() {
            fmt.Println(theIdx, theItem)
        }()
    }
    time.Sleep(time.Second)
}
  1. 将值传递给 goroutine,并在函数中添加参数:
package main

import "fmt"
import "time"

func main() {
    l := []int{1, 2, 3}
    for idx, item := range l {
        go func(idx, item int) {
            fmt.Println(idx, item)
        }(idx, item)
    }
    time.Sleep(time.Second)
}
英文:

You either need to re-assign the variable in the local context so that the closure can capture the values:

http://play.golang.org/p/-NO4S4qCZf

package main

import "fmt"
import "time"

func main() {
	l := []int{1, 2, 3}
	for idx, item := range l {
		theIdx, theItem := idx, item
		go func() {
			fmt.Println(theIdx, theItem)
		}()
	}
	time.Sleep(time.Second)
}

or you pass the values to the goroutine and add parameters to the function

http://play.golang.org/p/5gNToDWSQR

package main

import "fmt"
import "time"

func main() {
	l := []int{1, 2, 3}
	for idx, item := range l {
		go func(idx, item int) {
			fmt.Println(idx, item)
		}(idx, item)
	}
	time.Sleep(time.Second)
}

答案2

得分: 9

这是可以预料到的,并且在Go语言的“常见错误”页面中有记录。你可以对设计决策提出异议,但这是一个已知的影响。

推荐的做法是将这些值作为参数传递进去。

英文:

This is expected, and documented in the Go "Common Mistakes" page. You can argue with the design decision, but it is a known effect.

A recommended way to do this is to pass the values in as parameters.

答案3

得分: 2

第一个goroutine执行时,idxitem已经被下一个值覆盖了。由于这些例程在同一作用域内执行,你会得到新的值。

事后看来这是有道理的。

英文:

By the time the first goroutine is executed, idx and item have already been overwritten with the next values. As the routines are executed within the same scope, you get the new values.

It does make sense, in hindsight.

答案4

得分: 0

请注意,将指针作为参数传递给Go协程仍然存在问题:

不起作用的代码示例:

package main

import "fmt"
import "time"

func main() {
    l := []int{1, 2, 3}
    for idx, item := range l {
        go func(idx int, itemAddr *int) {
            fmt.Println(idx, *itemAddr)
        }(idx, &item)
    }
    time.Sleep(time.Second)
}

因此,在这种情况下,重新分配本地变量是解决方法:

起作用的代码示例:

package main

import "fmt"
import "time"

func main() {
    l := []int{1, 2, 3}
    for idx, item := range l {
        theIdx, theItem := idx, item
        go func() {
            theItemAddr := &theItem
            fmt.Println(theIdx, *theItemAddr)
        }()
    }
    time.Sleep(time.Second)
}

以上是要翻译的内容。

英文:

Note that passing pointers in as parameters to a go routine is still problematic:

Does NOT work:

https://play.golang.org/p/kAnJsIJCyNF

package main

import "fmt"
import "time"

func main() {
	l := []int{1, 2, 3}
	for idx, item := range l {
		go func(idx int, itemAddr *int) {
			fmt.Println(idx, *itemAddr)
		}(idx, &item)
	}
	time.Sleep(time.Second)
}

So reassigning variables locally is the way to go in this case:

WORKS:

https://play.golang.org/p/wgNFDi5TN-9

package main

import "fmt"
import "time"

func main() {
	l := []int{1, 2, 3}
	for idx, item := range l {
		theIdx, theItem := idx, item
		go func() {
			theItemAddr := &theItem
			fmt.Println(theIdx, *theItemAddr)
		}()
	}
	time.Sleep(time.Second)
}

huangapple
  • 本文由 发表于 2014年6月13日 08:58:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/24196200.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定