使用for循环进行并发,匿名函数意外行为

时间:2016-04-21 17:11:17

标签: go

我已经找到了一种方法让代码按照我的意愿运行,但我想了解为什么它的行为如此,以便我对Go并发性的理解得到改善。

我正在测试sync.WaitGroup等待一些goroutines完成,因为我计划以这种方式多次上传到Amazon S3。

这是我原来的代码:

func main() {

    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func() {
            fmt.Println(i)
            time.Sleep(time.Second * 1)
            wg.Done()
        }()
    }

    wg.Wait()
}

我很惊讶地看到输出是:6, 6, 6, 6, 6

而不是像2, 4, 1, 5, 3

由于循环甚至没有达到6,这对我来说没有任何意义。 我后来将i变量作为参数传递给匿名函数 然后它表现得像我预期的那样。

为什么会这样?我不明白。

2 个答案:

答案 0 :(得分:9)

常见问题解答:What happens with closures running as goroutines?

在这种情况下,在for循环完成之前,没有任何goroutine被调度。为了使for循环中断i不能小于或等于5,因此在那时它是6。当goroutines运行时,它们每个都打印在闭包中捕获的单个变量i的值。

i作为参数传递给函数时,将当前值复制到新变量,捕获此时的值。

答案 1 :(得分:0)

要回答您的问题,您必须将i传递到func,以便每个例程都有自己的i值副本。

所以你的代码应该是这样的

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func(i int) {
            fmt.Println(i)
            time.Sleep(time.Second * 1)
            wg.Done()
        }(i)
    }

    wg.Wait()
}

我编写了这个实用程序函数来帮助并行化一组函数:

import "sync"

// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
    var waitGroup sync.WaitGroup
    waitGroup.Add(len(functions))

    defer waitGroup.Wait()

    for _, function := range functions {
        go func(copy func()) {
            defer waitGroup.Done()
            copy()
        }(function)
    }
}

所以在你的情况下,我们可以做到这一点

func main() {
    functions := []func(){}
    for i := 1; i <= 5; i++ {
            function := func(i int) func() {
                return func() {
                        fmt.Println(i)
                }
            }(i)

        functions = append(functions, function)
    }

    Parallelize(functions...)

    fmt.Println("Done")
}

如果您想使用Parallelize功能,可以在https://github.com/shomali11/util

找到它