-4
import "fmt"
import "time"

func main() {
    array := []int{1, 2, 3}
    for _, num := range array {
        go func() {
            fmt.Println(fucknum)
        }(fucknum)
        time.Sleep(time.Nanosecond)
    }
    time.Sleep(time.Second)
}

Since there is a time.Sleep within the for-loop, I was expecting the output to be 1 2 3 because of the yielding of execution at each time.Sleep.

However, the output of this piece of code outputs 2 1 3. And after I changed Nanosecond to Microsecond, it turns to be 1 2 3.

For comparison, I also tested python3's asyncio version in which I assume asyncio.call_soon is equivalent to Go's non-IO coroutine.

import asyncio

loop = asyncio.get_event_loop()
async def test():
    for i in range(1, 4):
        # replace call_soon with asyncio.sleep(0) does not change the result
        loop.call_soon(lambda : print(i))
        await asyncio.sleep(0)

loop.run_until_complete(test())

And the outputs are always 1 2 3(This output is the same as what I expected as I know that internally, functions scheduled by call_soon is simply added into a FIFO queue)

How to explain the behavior of the Go version?

BAKE ZQ
  • 765
  • 6
  • 22
  • 1
    The timing of goroutines is not guaranteed, not is the order. A nanosecond or a microsecond might not be enough for the goroutines to launch and run the print statements. – Marc Oct 02 '20 at 12:47
  • 1
    [CommonMistakes: Using goroutines on loop iterator variables](https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables). – Peter Oct 02 '20 at 12:52
  • @Marc So the scheduling mechanism is different than python's asyncio? – BAKE ZQ Oct 02 '20 at 12:54
  • I have no idea how asyncio works, but a goroutine will take some time to setup and run after the `go` statement has returned. When it is scheduled is also not guaranteed in any way. – Marc Oct 02 '20 at 13:43
  • @Marc Thanks, can you write this into an answer, so I can accept. – BAKE ZQ Oct 02 '20 at 13:45
  • 1
    This has been answered a few times, such as https://stackoverflow.com/questions/39818254/goroutines-order-of-execution – Marc Oct 02 '20 at 13:49
  • @Marc ok, I will delete this question. Thanks again for your attention. – BAKE ZQ Oct 02 '20 at 13:50

1 Answers1

2

Because each of the generated goroutines are accessing the same variable defined outside of their scope they are not allocating a new memory address, but are referencing the same address. The for loop in your example actually references the same variable multiple times. By introducing a local scope in the goroutine definition, each time a new goroutine is spawned it will allocate a new variable on each iteration.

To solve the problem you need to pass the index as a parameter for your closure function.

This should fix your problem.

func main() {
    array := []int{1, 2, 3}
    for _, num := range array {
        go func(num int) {
            fmt.Println(num)
        }(num)
        time.Sleep(time.Nanosecond)
    }
    time.Sleep(time.Second)
}
Endre Simo
  • 11,330
  • 2
  • 40
  • 49
  • 1
    This. The `num` gets changed before it's printed in another goroutine. That can be prevented by sending a parameter to the function that gets executed in another goroutine. But still, in this example it can't be guaranteed that the order will be `1 2 3`, it can as well be `2 1 3` or `3 2 1` or whatever, based on the order the goroutines get executed. If we want to ensure the order, we would have to block. Although in that case we might as well not use the concurrency. – Z. Kosanovic Oct 02 '20 at 12:41
  • Why the order cannot be ensured? – BAKE ZQ Oct 02 '20 at 12:43
  • 1
    Because the goroutines are executed at the same time, they aren't waiting for each other. – Z. Kosanovic Oct 02 '20 at 12:46
  • 3
    Because the order of execution of goroutines is not guaranteed. They're going to be created suspended, then when a time slice is available a scheduler will run one of them, but the scheduling policy is not strictly specified, especially given work-stealing. – Masklinn Oct 02 '20 at 12:46
  • 1
    The point of concurrency is that your code does not *care* what order the code is executed in; from your code's perspective, it is executed simultaneously. – Adrian Oct 02 '20 at 13:02
  • @Adrian It seems like to be this, I am using python before and I know that the ordering of coroutines follows a FIFO way, But this does not seem to be the same in GO. – BAKE ZQ Oct 02 '20 at 13:10
  • @BAKEZQ it does because your sleep duration is 5 milliseconds in your Python code, which is enough to execute coroutine. Increase your sleep duration in your Go code to `5 * time.Milliseconds` and it will behave the same. – Z. Kosanovic Oct 02 '20 at 13:12
  • @Masklinn Can you add up an answer so that I can accept yours. – BAKE ZQ Oct 02 '20 at 13:12
  • 2
    @BAKEZQ they don't naturally execute FIFO; if they did, there would be literally no point in concurrency. They only execute this way because you're artificially forcing it with sleeps, ensuring that your concurrent code does not run concurrently. – Adrian Oct 02 '20 at 13:13
  • @Adrian I mean NON-IO function. For the case of IO version, the callbacks are also runs in a FIFO way. – BAKE ZQ Oct 02 '20 at 13:14
  • @Z.Kosanovic check my updated python version. It doesn't matter what the duration time is. – BAKE ZQ Oct 02 '20 at 13:18