英文:
What is the best way to create goroutines dynamically?
问题
抱歉,如果这是一个新手问题,但我是一个新手;)。我有以下的游乐场。如何动态创建我的goroutines?我的第一个集合在游乐场中按预期工作,但我的第二个集合对于每个值都返回"11"。我可以通过取消注释第38行来解决这个问题,但这似乎有点像一个hack。有没有更常用的方法来动态创建我的goroutines?
package main
import (
"fmt"
"log"
"time"
)
func myFunc(i int) int {
return i
}
func first() {
firstChannel := make(chan int)
go func() { firstChannel <- myFunc(0) }()
go func() { firstChannel <- myFunc(1) }()
go func() { firstChannel <- myFunc(2) }()
go func() { firstChannel <- myFunc(3) }()
go func() { firstChannel <- myFunc(4) }()
go func() { firstChannel <- myFunc(5) }()
go func() { firstChannel <- myFunc(6) }()
go func() { firstChannel <- myFunc(7) }()
go func() { firstChannel <- myFunc(8) }()
go func() { firstChannel <- myFunc(9) }()
go func() { firstChannel <- myFunc(10) }()
for k := 0; k < 11; k++ {
select {
case result := <-firstChannel:
log.Println(result)
}
}
}
func second() {
secondChannel := make(chan int)
for j := 0; j < 11; j++ {
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
for k := 0; k < 11; k++ {
select {
case result := <-secondChannel:
log.Println(result)
}
}
}
func main() {
fmt.Println("First set------------------")
first()
time.Sleep(1 * time.Second)
fmt.Println("Second set------------------")
second()
}
结果:
First set------------------
2009/11/10 23:00:00 0
2009/11/10 23:00:00 1
2009/11/10 23:00:00 2
2009/11/10 23:00:00 3
2009/11/10 23:00:00 4
2009/11/10 23:00:00 5
2009/11/10 23:00:00 6
2009/11/10 23:00:00 7
2009/11/10 23:00:00 8
2009/11/10 23:00:00 9
2009/11/10 23:00:00 10
Second set------------------
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
英文:
Sorry if this is a newb question but I'm a newb ;). I have the following playground. How do I create my goroutines dynamically? My first set in the playground works as expected but my second set returns "11" for each value. I can solve it by uncommenting line 38 but that seems kind of like a hack. Is there a more preferred way to create my goroutines dynamically?
package main
import (
"fmt"
"log"
"time"
)
func myFunc(i int) int {
return i
}
func first() {
firstChannel := make(chan int)
go func() { firstChannel <- myFunc(0) }()
go func() { firstChannel <- myFunc(1) }()
go func() { firstChannel <- myFunc(2) }()
go func() { firstChannel <- myFunc(3) }()
go func() { firstChannel <- myFunc(4) }()
go func() { firstChannel <- myFunc(5) }()
go func() { firstChannel <- myFunc(6) }()
go func() { firstChannel <- myFunc(7) }()
go func() { firstChannel <- myFunc(8) }()
go func() { firstChannel <- myFunc(9) }()
go func() { firstChannel <- myFunc(10) }()
for k := 0; k < 11; k++ {
select {
case result := <-firstChannel:
log.Println(result)
}
}
}
func second() {
secondChannel := make(chan int)
for j := 0; j < 11; j++ {
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
for k := 0; k < 11; k++ {
select {
case result := <-secondChannel:
log.Println(result)
}
}
}
func main() {
fmt.Println("First set------------------")
first()
time.Sleep(1 * time.Second)
fmt.Println("Second set------------------")
second()
}
Result:
First set------------------
2009/11/10 23:00:00 0
2009/11/10 23:00:00 1
2009/11/10 23:00:00 2
2009/11/10 23:00:00 3
2009/11/10 23:00:00 4
2009/11/10 23:00:00 5
2009/11/10 23:00:00 6
2009/11/10 23:00:00 7
2009/11/10 23:00:00 8
2009/11/10 23:00:00 9
2009/11/10 23:00:00 10
Second set------------------
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
2009/11/10 23:00:01 11
答案1
得分: 8
使用循环
for j := 0; j < 11; j++ {
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
你创建了一个闭包,j
变量在 second()
函数和在循环内部创建的函数之间共享(参见这里)- 因此,在引用 j
变量时存在竞态条件。
在循环结束之前,goroutine 可能不会开始执行,然后它们都会看到循环结束时的 j
变量。
你可以通过使每个 goroutine 引用一个单独的变量来解决这个问题:
for j := 0; j < 11; j++ {
arg := j
go func() { secondChannel <- myFunc(arg) }()
//time.Sleep(1*time.Millisecond)
}
现在,在循环中由 func()
创建的闭包引用了 arg
变量,每次迭代都会有一个单独的变量。
或者你可以通过将变量复制到函数的参数中来传递它:
for j := 0; j < 11; j++ {
go func(arg int) { secondChannel <- myFunc(arg) }(j)
//time.Sleep(1*time.Millisecond)
}
英文:
With the loop
for j := 0; j < 11; j++ {
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
you create a closure, and the j
variable becomes shared between your second()
function and the functions you create inside the loop that runs in separate go routines.(See e.g. here) - so there's a race condition when referring to the j
variable.
The go routines might not start executing before the loop is finished, and then the will all see the j variable as it is at the end of the loop.
You can fix this by making each go routine refer to a separate variable:
for j := 0; j < 11; j++ {
arg := j
go func() { secondChannel <- myFunc(arg) }()
//time.Sleep(1*time.Millisecond)
}
Now the closure created by the func() in the loop refers to the arg
variable, which will be separate for each iteration.
Or you can pass it by copying the variable to an argument of your func():
for j := 0; j < 11; j++ {
go func(arg int) { secondChannel <- myFunc(arg) }(j)
//time.Sleep(1*time.Millisecond)
}
答案2
得分: 3
《Go编程语言规范》
函数字面量
函数字面量是闭包:它们可以引用在周围函数中定义的变量。这些变量在周围函数和函数字面量之间共享,并且只要它们可访问,它们就会一直存在。
闭包在并发使用时可能会引起一些混淆。
为了将v的当前值绑定到每个闭包中,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包。
更简单的方法是只需创建一个新变量,使用一种可能看起来奇怪但在Go中运行良好的声明样式。
参见《Go中捕获闭包(for循环变量)》。
使用j := j
。例如,
package main
import (
"fmt"
"log"
"time"
)
func myFunc(i int) int {
return i
}
func first() {
firstChannel := make(chan int)
go func() { firstChannel <- myFunc(0) }()
go func() { firstChannel <- myFunc(1) }()
go func() { firstChannel <- myFunc(2) }()
go func() { firstChannel <- myFunc(3) }()
go func() { firstChannel <- myFunc(4) }()
go func() { firstChannel <- myFunc(5) }()
go func() { firstChannel <- myFunc(6) }()
go func() { firstChannel <- myFunc(7) }()
go func() { firstChannel <- myFunc(8) }()
go func() { firstChannel <- myFunc(9) }()
go func() { firstChannel <- myFunc(10) }()
for k := 0; k < 11; k++ {
select {
case result := <-firstChannel:
log.Println(result)
}
}
}
func second() {
secondChannel := make(chan int)
for j := 0; j < 11; j++ {
j := j
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
for k := 0; k < 11; k++ {
select {
case result := <-secondChannel:
log.Println(result)
}
}
}
func main() {
fmt.Println("First set------------------")
first()
time.Sleep(1 * time.Second)
fmt.Println("Second set------------------")
second()
}
输出:
First set------------------
2009/11/10 23:00:00 0
2009/11/10 23:00:00 1
2009/11/10 23:00:00 2
2009/11/10 23:00:00 3
2009/11/10 23:00:00 4
2009/11/10 23:00:00 5
2009/11/10 23:00:00 6
2009/11/10 23:00:00 7
2009/11/10 23:00:00 8
2009/11/10 23:00:00 9
2009/11/10 23:00:00 10
Second set------------------
2009/11/10 23:00:01 0
2009/11/10 23:00:01 1
2009/11/10 23:00:01 2
2009/11/10 23:00:01 3
2009/11/10 23:00:01 4
2009/11/10 23:00:01 5
2009/11/10 23:00:01 6
2009/11/10 23:00:01 7
2009/11/10 23:00:01 8
2009/11/10 23:00:01 9
2009/11/10 23:00:01 1
英文:
> The Go Programming Language Specification
>
> Function literals
>
> Function literals are closures: they may refer to variables defined in
> a surrounding function. Those variables are then shared between the
> surrounding function and the function literal, and they survive as
> long as they are accessible.
>
> What happens with closures running as goroutines?
>
> Some confusion may arise when using closures with concurrency.
>
> To bind the current value of v to each closure as it is launched, one
> must modify the inner loop to create a new variable each iteration.
> One way is to pass the variable as an argument to the closure.
>
> Even easier is just to create a new variable, using a declaration
> style that may seem odd but works fine in Go.
See Captured Closure (for Loop Variable) in Go.
Use j := j
. For example,
package main
import (
"fmt"
"log"
"time"
)
func myFunc(i int) int {
return i
}
func first() {
firstChannel := make(chan int)
go func() { firstChannel <- myFunc(0) }()
go func() { firstChannel <- myFunc(1) }()
go func() { firstChannel <- myFunc(2) }()
go func() { firstChannel <- myFunc(3) }()
go func() { firstChannel <- myFunc(4) }()
go func() { firstChannel <- myFunc(5) }()
go func() { firstChannel <- myFunc(6) }()
go func() { firstChannel <- myFunc(7) }()
go func() { firstChannel <- myFunc(8) }()
go func() { firstChannel <- myFunc(9) }()
go func() { firstChannel <- myFunc(10) }()
for k := 0; k < 11; k++ {
select {
case result := <-firstChannel:
log.Println(result)
}
}
}
func second() {
secondChannel := make(chan int)
for j := 0; j < 11; j++ {
j := j
go func() { secondChannel <- myFunc(j) }()
//time.Sleep(1*time.Millisecond)
}
for k := 0; k < 11; k++ {
select {
case result := <-secondChannel:
log.Println(result)
}
}
}
func main() {
fmt.Println("First set------------------")
first()
time.Sleep(1 * time.Second)
fmt.Println("Second set------------------")
second()
}
Output:
<pre>
First set------------------
2009/11/10 23:00:00 0
2009/11/10 23:00:00 1
2009/11/10 23:00:00 2
2009/11/10 23:00:00 3
2009/11/10 23:00:00 4
2009/11/10 23:00:00 5
2009/11/10 23:00:00 6
2009/11/10 23:00:00 7
2009/11/10 23:00:00 8
2009/11/10 23:00:00 9
2009/11/10 23:00:00 10
Second set------------------
2009/11/10 23:00:01 0
2009/11/10 23:00:01 1
2009/11/10 23:00:01 2
2009/11/10 23:00:01 3
2009/11/10 23:00:01 4
2009/11/10 23:00:01 5
2009/11/10 23:00:01 6
2009/11/10 23:00:01 7
2009/11/10 23:00:01 8
2009/11/10 23:00:01 9
2009/11/10 23:00:01 1
</pre>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论