通过迭代函数切片调用每个函数

时间:2017-11-24 05:47:32

标签: go slice goroutine

我试图循环一片函数,然后调用其中的每个函数。但是我得到了奇怪的结果。这是我的代码:

package main

import (
    "fmt"
    "sync"
)

func A() {
    fmt.Println("A")
}

func B() {
    fmt.Println("B")
}

func C() {
    fmt.Println("C")
}

func main() {
    type fs func()
    var wg sync.WaitGroup
    f := []fs{A, B, C}
    for a, _ := range f {
        wg.Add(1)
        go func() {
            defer wg.Done()
            f[a]()
        }()
    }
    wg.Wait()
}

我原以为它会调用函数A,B然后调用C,但我的输出只得到Cs。

C
C
C

请提出错误及其背后的逻辑。我怎样才能得到理想的行为。

Go Playground

2 个答案:

答案 0 :(得分:5)

Classic go gotcha :)

Go Go FAQ

for a, _ := range f {
    wg.Add(1)
    a:=a // this will make it work
    go func() {
        defer wg.Done()
        f[a]()
    }()
}

您的func() {}()是一个关闭a的关闭。并且a是所有go func go例程的共享,因为for循环重用相同的var(在内存中意味着相同的地址,因此值相同),所以它们自然都会看到{{1}的最后一个值}。

解决方案要么在关闭之前重新声明a(如上所述)。这将创建新的var(内存中的新地址),然后每次调用a:=a时都是新的。

或者将其作为参数传递给go函数,在这种情况下,您传递go func值的副本,如下所示:

a

你甚至不需要去例行https://play.golang.org/p/nkP9YfeOWF,例如演示同样的问题。这里的关键是'封闭'。

答案 1 :(得分:2)

问题似乎是你没有将所需的值传递给goroutine,而变量值是从外部范围获取的。话虽这么说,范围迭代甚至在第一个goroutine执行之前就完成了,这就是为什么你总是得到索引a == 2,即函数C. 如果你只是将time.Sleep(100)放在你的范围内,只是为了让goroutine能够在继续下一次迭代之前赶上主线程,你可以测试这个 - > GO playground

for a, _ := range f {
    wg.Add(1)
    go func() {
        defer wg.Done()
        f[a]()
    }()
    time.Sleep(100)
}

输出

A
B
C

虽然你想要做的只是简单地将一个参数传递给goroutine,它将为该函数制作一个副本。

func main() {
    type fs func()
    var wg sync.WaitGroup
    f := []fs{A, B, C}
    for _, v := range f {
        wg.Add(1)
        go func(f fs) {
            defer wg.Done()
            f()
        }(v)
    }
    wg.Wait()
}

GO Playground