英文:
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 <= 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 <= 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 "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)
}
}
So in your case, we could do this
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")
}
If you wanted to use the Parallelize function, you can find it here https://github.com/shomali11/util
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论