6

in the interests of learning more about Go, I have been playing with goroutines, and have noticed something - but am not sure what exactly I'm seeing, and hope someone out there might be able to explain the following behaviour.

the following code does exactly what you'd expect:

package main

import (
  "fmt"
)

type Test struct {
  me int
}

type Tests []Test

func (test *Test) show() {
  fmt.Println(test.me)

}

func main() {
  var tests Tests
  for i := 0; i < 10; i++ {
    test := Test{
      me: i,
    }
    tests = append(tests, test)
  }

  for _, test := range tests {
    test.show()
  }

}

and prints 0 - 9, in order.

now, when the code is changed as shown below, it always returns with the last one first - doesn't matter which numbers I use:

package main

import (
    "fmt"
    "sync"
)

type Test struct {
    me int
}

type Tests []Test

func (test *Test) show(wg *sync.WaitGroup) {
    fmt.Println(test.me)
    wg.Done()

}

func main() {
    var tests Tests
    for i := 0; i < 10; i++ {
        test := Test{
            me: i,
        }
        tests = append(tests, test)
    }

    var wg sync.WaitGroup
    wg.Add(10)
    for _, test := range tests {
        go func(t Test) {
            t.show(&wg)
        }(test)
    }
    wg.Wait()

}

this will return: 9 0 1 2 3 4 5 6 7 8

the order of iteration of the loop isn't changing, so I guess that it is something to do with the goroutines... basically, I am trying to understand why it behaves like this...I understand that goroutines can run in a different order than the order in which they're spawned, but, my question is why this always runs like this. as if there's something really obvious I'm missing...

5
  • which go version are you using? Commented Feb 2, 2016 at 12:11
  • sorry, 1.5.2 - locally, and whatever is running on the playground Commented Feb 2, 2016 at 12:14
  • 1
    I would guess this is because go doesn't randomize how goroutines are scheduled nor does the operating system randomize how it schedules threads. And the data structures/algorithms they use internally are quite deterministic. But. Add some more work into the goroutines, maybe some waiting for I/O, put some load on the system, maybe an interrupt at the wrong time and the ordering will become much less predictable. Commented Feb 2, 2016 at 12:29
  • makes sense - I suspect that it's just the fact that with "no load", they seem to run in a predictable order, with all of them in the order you'd expect, apart from the last one, which runs first. I am not relying on goroutines running in order, but just wanted to make sure that I wasn't missing something obvious. Commented Feb 2, 2016 at 12:41
  • the strangeness go further - putting 1 second sleep on your show function changes all the order: 0 9 8 7 6 5 4 3 2 1 play.golang.org/p/pMpOxgp59h - now they are even sorted in the other order but the '0' come as first... why? Commented Feb 2, 2016 at 12:45

2 Answers 2

3

As expected, the ouput is pseudo-random,

package main

import (
    "fmt"
    "runtime"
    "sync"
)

type Test struct {
    me int
}

type Tests []Test

func (test *Test) show(wg *sync.WaitGroup) {
    fmt.Println(test.me)
    wg.Done()

}

func main() {
    fmt.Println("GOMAXPROCS", runtime.GOMAXPROCS(0))
    var tests Tests
    for i := 0; i < 10; i++ {
        test := Test{
            me: i,
        }
        tests = append(tests, test)
    }

    var wg sync.WaitGroup
    wg.Add(10)
    for _, test := range tests {
        go func(t Test) {
            t.show(&wg)
        }(test)
    }
    wg.Wait()

}

Output:

$ go version
go version devel +af15bee Fri Jan 29 18:29:10 2016 +0000 linux/amd64
$ go run goroutine.go
GOMAXPROCS 4
9
4
5
6
7
8
1
2
3
0
$ go run goroutine.go
GOMAXPROCS 4
9
3
0
1
2
7
4
8
5
6
$ go run goroutine.go
GOMAXPROCS 4
1
9
6
8
4
3
0
5
7
2
$ 

Are you running in the Go playground? The Go playground, by design, is deterministic, which makes it easier to cache programs.

Or, are you running with runtime.GOMAXPROCS = 1? This runs one thing at a time, sequentially. This is what the Go playground does.

Sign up to request clarification or add additional context in comments.

2 Comments

It took me 10 runs to get any change in order - but yes, If I'd run it more, I'd have seen this. it was the fact that the last consistently comes first (in this case) - just wanted to make sure that I didn't have some kind of warped understanding of how goroutines work...so, will accept this and close.
the comment about the playground was spot on.
2

Go routines are scheduled randomly since Go 1.5. So, even if the order looks consistent, don't rely on it.

See Go 1.5 release note :

In Go 1.5, the order in which goroutines are scheduled has been changed. The properties of the scheduler were never defined by the language, but programs that depend on the scheduling order may be broken by this change. We have seen a few (erroneous) programs affected by this change. If you have programs that implicitly depend on the scheduling order, you will need to update them.

Another potentially breaking change is that the runtime now sets the default number of threads to run simultaneously, defined by GOMAXPROCS, to the number of cores available on the CPU. In prior releases the default was 1. Programs that do not expect to run with multiple cores may break inadvertently. They can be updated by removing the restriction or by setting GOMAXPROCS explicitly. For a more detailed discussion of this change, see the design document.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.