Golang:循环下的闭包(翻译)

大河翻腾 · · 1912 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

原文地址:https://github.com/golang/go/wiki/CommonMistakes#wiki-pages-box

Introduction(介绍)

When new programmers start using Go or when old Go programmers start using a new concept, there are some common mistakes that many of them make. Here is a non-exhaustive list of some frequent mistakes that show up on the mailing lists and in IRC.Using goroutines on loop iterator variables

当工程师刚开始使用Go或者Go工程师刚开始接触到一个新的概念的时候,他们中的很多人对这些概念会产生类似的误解。下文就邮件列表和IRC中一些常见的问题给出简要的解释。迭代变量用在循环下的go协程上。

When iterating in Go, one might attempt to use goroutines to process data in parallel. For example, you might write something like this, using a closure:

当使用Go语言处理迭代时,有人可能会尝试使用协程来并行处理数据。例如,你可能会使用闭包写下面的代码:

for _, val := range values {
    go func() {
        fmt.Println(val)
    }()
}

The above for loops might not do what you expect because their val variable is actually a single variable that takes on the value of each slice element. Because the closures are all only bound to that one variable, there is a very good chance that when you run this code you will see the last element printed for every iteration instead of each value in sequence, because the goroutines will probably not begin executing until after the loop.

以上的代码或许不会按照你的预期运行,因为他们的val变量实际上是值为每一个切片元素值得单一变量。因为这些闭包都绑定到了这一个变量,那么很可能当你执行这段代码得时候每次迭代你都只看到打印出来得序列中最后一个数值,因为协程很可能知道循环结束之后才会执行。

The proper way to write that closure loop is:

正确的编写闭包循环的方式应该是:

for _, val := range values {
    go func(val interface{}) {
        fmt.Println(val)
    }(val)
}

By adding val as a parameter to the closure, val is evaluated at each iteration and placed on the stack for the goroutine, so each slice element is available to the goroutine when it is eventually executed.

通过将val作为一个参数添加到闭包里头,val被取值并存放至协程的栈上,所以当协程最终执行的时候,每一个切片元素对协程来说都是可用的。

It is also important to note that variables declared within the body of a loop are not shared between iterations, and thus can be used separately in a closure. The following code uses a common index variable i to create separate vals, which results in the expected behavior:

同样值得关注的是,在循环体中被声明的变量并不在每次的迭代中共享,因此可以被单独的用到闭包当中。下面的代码使用了一个共同的索引变量i来创建单独的val,这样会产生预期的结果。

for i := range valslice {
    val := valslice[i]
    go func() {
        fmt.Println(val)
    }()
}

Note that without executing this closure as a goroutine, the code runs as expected. The following example prints out the integers between 1 and 10.

注意下在没有将闭包用协程来执行时,代码会按照预期执行。下面的例子打印出了数字1到10.

for i := 1; i <= 10; i++ {
    func() {
        fmt.Println(i)
    }()
}

Even though the closures all still close over the same variable (in this case, i), they are executed before the variable changes, resulting in the desired behavior.

即使这些闭包们仍然绑定到同样的变量(在这个例子中),他们却在变量改变之前执行,产生了想要的结果。

Another similar situation that you may find like following:

另外一个类似的场景例如如下:

for _, val := range values {
    go val.MyMethod()
}

func (v *val) MyMethod() {
        fmt.Println(v)
}

The above example also will print last element of values, the reason is same as closure. To fix the issue declare another variable inside the loop.

上面的例子同样打印values里的最后一个元素,原因和闭包是一样的。为了修复这个问题,可以在循环里头声明另一个变量。

for _, val := range values {
        newVal := val
    go newVal.MyMethod()
}

func (v *val) MyMethod() {
        fmt.Println(v)
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:大河翻腾

查看原文:Golang:循环下的闭包(翻译)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1912 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传