Go并发中使用for循环和匿名函数的行为出现了意外情况。

huangapple go评论97阅读模式
英文:

Go concurrency with for loop and anonymous function behaves unexpectedly

问题

我已经找到了一种让代码按照我想要的方式运行的方法,但我想要了解为什么它会以这种方式运行,以便提高我对Go并发的理解。

我正在测试sync.WaitGroup来等待一些goroutine完成,因为我计划以这种方式进行多次上传到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变量作为参数传递给匿名函数,然后它按照我预期的方式运行。

为什么会发生这种情况?我不理解。

英文:

I have already found out a way for the code to behave as I want, but I would like to understand why it behaves like this so that my understanding of Go concurrency improves.

I was testing out sync.WaitGroup to wait for some goroutines to finish because I plan on doing multiple uploads to Amazon S3 in this way.

This was the code I had originally:

func main() {

	var wg sync.WaitGroup

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

	wg.Wait()
}

I was surprised to see that the output was: 6, 6, 6, 6, 6.

Instead of something like: 2, 4, 1, 5, 3.

Since the loop does not even go to 6, this made no sense to me.
I later passed the i variable to the anonymous function as argument
and then it behaved as I expected.

Why does this happen? I don't understand it.

答案1

得分: 11

这在常见问题解答中有提到:闭包作为goroutine运行时会发生什么?

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

当你将i作为参数传递给函数时,你将当前值复制到一个新变量中,捕获那一刻的值。

英文:

This is covered in the faq: What happens with closures running as goroutines?

In this case, none of the goroutines get scheduled until the for loop completes. In order for the for loop to break i must not be less than or equal to 5, therefore it is 6 at that point. When the goroutines run, they each print the value of the single variable i which is captured in the closures.

When you pass i as an argument to the function, you copy the current value to a new variable, capturing the value at that moment.

答案2

得分: 1

为了回答你的问题,你需要将 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 并行化函数调用
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

英文:

To answer your question, you have to pass i into your func so that each routine would have its own copy of the value of i.

So your code should look like this

func main() {
    var wg sync.WaitGroup

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

    wg.Wait()
}

I wrote this utility function to help parallelize a group of functions:

import &quot;sync&quot;

// 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)
    }
}

So in your case, we could do this

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

		functions = append(functions, function)
	}

    Parallelize(functions...)

	fmt.Println(&quot;Done&quot;)
}

If you wanted to use the Parallelize function, you can find it here https://github.com/shomali11/util

huangapple
  • 本文由 发表于 2016年4月22日 01:11:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/36776315.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定