Go语言中常见错误

介绍

下列的一些Go语言常见错误可能对新手学习Go语言有很大帮助

使用引用循环迭代器变量

在Go中,循环迭代器变量在每一个循环中都会被赋予不同的值,这虽然有效,但是也会带来意想不到的错误,比如下面的例子

func main() {
    var out []*int
    for i := 0; i < 3; i++ {
        out = append(out, &i)
    }
    fmt.Println("Values:", *out[0], *out[1], *out[2])
    fmt.Println("Addresses:", out[0], out[1], out[2])
}

它将会输出

Values: 3 3 3
Addresses: 0x40e020 0x40e020 0x40e020

解释:在每次迭代中,我们将i的地址附加到out切片中,但由于它是相同的变量,所以我们将附加相同的地址,最终包含分配给i的最后一个值。解决方案之一是将循环变量复制到一个新变量中:

 for i := 0; i < 3; i++ {
+    i := i // Copy i into a new variable.
     out = append(out, &i)
 }

此时的输出是符合预期的
解释:行i:= i将循环变量i复制到一个新的变量作用域,该变量作用域位于for循环体块中,也称为i。新变量的地址是被附加到数组中的地址,这使得它比for循环体块的寿命更长。在每个循环迭代中创建一个新变量。

虽然这个例子可能看起来有点明显,但同样的意外行为可能在其他一些情况下隐藏得更多。例如,循环变量可以是一个数组,引用可以是一个切片:

func main() {
    var out [][]int
    for _, i := range [][1]int{{1}, {2}, {3}} {
        out = append(out, i[:])
    }
    fmt.Println("Values:", out)
}

输出:

Values: [[3] [3] [3]]

当循环变量在Goroutine中使用时,也会出现同样的问题(参见下一节)。

在循环迭代器变量上使用 goroutines

在Go中迭代时,可能会尝试使用goroutine并行处理数据。例如,你可以这样写,使用闭包:

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

上面的for循环可能不会达到您的预期效果,因为它们的val变量实际上是一个单独的变量,它接受每个slice元素的值。因为闭包都只绑定到那个变量,所以在运行这段代码时,很有可能看到每次迭代都打印最后一个元素,而不是按顺序打印每个值,因为goroutine很可能直到循环结束后才开始执行。

写这个闭包的正确方法是:

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

通过将val作为参数添加到闭包中,val将在每次迭代时被求值并放在goroutine的堆栈上,因此在最终执行goroutine时,每个slice元素都是可用的。

同样重要的是要注意,在循环体中声明的变量在迭代之间是不共享的,因此可以在闭包中单独使用。下面的代码使用一个公共索引变量i来创建单独的val,这将导致预期的行为:

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

注意,如果不将此闭包作为go协程执行,代码将按预期运行。下面的示例输出1到10之间的整数。

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

尽管闭包仍然对相同的变量关闭(在本例中为i),但它们在变量更改之前执行,从而产生所需的行为。
下面的例子也是类似的

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

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

上面的例子也将打印值的最后一个元素,原因与闭包相同。要修复该问题,在循环中声明另一个变量。

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

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

你可能感兴趣的:(go)