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

calling each function by iteration function slice

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

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

经典的陷阱:)

官方围棋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(意味着内存中的相同地址,因此具有相同的值),因此它们自然都会看到 [=13= 的最后一个值].

解决方案是在关闭之前重新声明 a:=a(如上)。这将创建新的 var(内存中的新地址),然后对于 go func.

的每次调用都是新的

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

go func(i int) {
    defer wg.Done()
    f[i]()
}(a)

您甚至不需要 go 例程,这个 https://play.golang.org/p/nkP9YfeOWF 例如演示了相同的陷阱。这里的关键是'closure'.

问题似乎是您没有将所需的值传递给 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,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