动态创建goroutines的最佳方法是什么?

时间:2014-11-07 16:25:31

标签: go

很抱歉,如果这是一个新问题,但我是新手;)。我有following playground。如何动态创建goroutines?我在操场上的第一套按预期工作,但我的第二套回归" 11"对于每个值。我可以通过取消注释第38行来解决它,但这看起来有点像黑客。有没有更优选的方式动态创建我的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

2 个答案:

答案 0 :(得分:8)

使用循环

for j := 0; j < 11; j++ {
    go func() { secondChannel <- myFunc(j) }()
    //time.Sleep(1*time.Millisecond)
}

你创建了一个闭包,j变量在你的second()函数和你在单独的go例程中运行的循环中创建的函数之间共享。(参见例如here) - 所以当提到j变量时会出现竞争状况。

在循环结束之前,go例程可能不会开始执行,然后所有人都会看到循环结束时的j变量。

你可以通过让每个例程引用一个单独的变量来解决这个问题:

for j := 0; j < 11; j++ {
    arg := j
    go func() { secondChannel <- myFunc(arg) }()
    //time.Sleep(1*time.Millisecond)
}

现在循环中func()创建的闭包引用arg变量,每次迭代都会分开。

或者您可以通过将变量复制到func()的参数来传递它:

for j := 0; j < 11; j++ {
    go func(arg int) { secondChannel <- myFunc(arg) }(j)
    //time.Sleep(1*time.Millisecond)
}

答案 1 :(得分:3)

  

The Go Programming Language Specification

     

Function literals

     

函数文字是闭包:它们可以引用在中定义的变量   周围的功能。然后在这些变量之间共享这些变量   周围函数和函数文字,它们存活下来   只要他们可以访问。

     

What happens with closures running as goroutines?

     

使用并发闭包时可能会出现一些混淆。

     

在启动时将v的当前值绑定到每个闭包,一个   必须修改内部循环以在每次迭代时创建一个新变量。   一种方法是将变量作为参数传递给闭包。

     

更简单的方法就是使用声明创建一个新变量   可能看似奇怪但在Go中工作正常的风格。

请参阅Captured Closure (for Loop Variable) in Go

使用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