user_78361084
user_78361084

Reputation: 3888

What is the best way to create goroutines dynamically?

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

Upvotes: 2

Views: 1496

Answers (2)

peterSO
peterSO

Reputation: 166539

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:

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

Upvotes: 3

nos
nos

Reputation: 229058

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

Upvotes: 8

Related Questions