4

I'm learning Go, and have run across the following code snippet:

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int, 2)
    go sum(a[0:3], c)
    go sum(a[3:6], c)
    x := <-c
    y := <-c
    // x, y := <-c, <-c // receive from c

    fmt.Println(x, y)
}

Output:

-5 17

Program exited.

Can someone please tell me why the 2nd calling of the "sum" function is coming through the channel before the 1st one? It seems to me that the output should be:

17 -5

I've also tested this with an un-buffered channel and it also gives the same order of output. What am I missing?

mikekehrli
  • 332
  • 3
  • 10
  • 1
    Possible duplicate of [Golang channel output order](https://stackoverflow.com/questions/50654576/golang-channel-output-order) – Himanshu Aug 24 '18 at 05:28

3 Answers3

4

You are calling the go routine in your code and you can't tell when the routine will end and the value will be passed to the buffered channel.

As this code is asynchronous so whenever the routine will finish it will write the data to the channel and will be read on the other side. In the example above you are calling only two go routines so the behavior is certain and same output is generated somehow for most of the cases but when you will increase the go routines the output will not be same and the order will be different unless you make it synchronous.

Example:

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 2, 4, 2, 8, 2, 7, 2, 99, -32, 2, 12, 32, 44, 11, 63}

    c := make(chan int)
    for i := 0; i < len(a); i = i + 5 {
        go sum(a[i:i+5], c)
    }
    output := make([]int, 5)
    for i := 0; i < 4; i++ {
        output[i] = <-c
    }
    close(c)

    fmt.Println(output)
}

The output for this code on different sample runs was

[12 18 0 78 162] 

[162 78 12 0 18]

[12 18 78 162 0]

This is because the goroutines wrote the output asynchronously to the buffered channel.

Hope this helps.

Hamza Anis
  • 2,475
  • 1
  • 26
  • 36
  • 1
    Thank you. This helped a great deal. I at least see now that the threads are not guaranteed to complete in the order they were started. However, it seems to me that at the very least, the 1st one started should complete before the 2nd one, at least some of the times. I only got this to occur when I introduced a time.Sleep(1) between the to go statements in my code snippet. – mikekehrli Aug 24 '18 at 04:29
2

When running the golang.org sandbox, I got the same result every time. As stated above. But when I ran the same snippet on in my own sandbox (on my computer), it sometimes changed the order of the threads. This is much more satisfactory. It shows I can't expect any particular order to thread execution, which is intuitive. I just couldn't figure out why I was getting the same order of execution, and it was the reverse of the order the threads were started. I think this was just luck of the draw on the golang.org's sandbox.

mikekehrli
  • 332
  • 3
  • 10
  • 3
    The reason you were getting the same result every time in the sandbox is that results of your program are cached. – Akavall Aug 24 '18 at 05:57
  • Ah, that makes sense. I'm very clear now on this issue. I was seeing something that appeared to be deterministic, which appeared not to be. I also ran this program on my own computer. As long as I dirtied the file somehow, the results were random, even for the original snippet in my first post – mikekehrli Aug 25 '18 at 17:49
1

Goroutines are started asynchronously and they can write to channel in any order. It is easier to see if you modify your example a little bit:

package main

import (
    "fmt"
    "time"
)

func sum(a []int, c chan int, name string, sleep int) {
    fmt.Printf("started goroutine: %s\n", name)
    time.Sleep(time.Second * time.Duration(sleep))

    sum := 0

    for _, v := range a { 
        sum += v
    }   
    fmt.Printf("about end goroutine: %s\n", name)
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int, 2)
    go sum(a[0:3], c, "A", 1)
    go sum(a[3:6], c, "B", 1)
    x := <-c 
    y := <-c 
    // x, y := <-c, <-c // receive from c

    fmt.Println(x, y)
}

https://play.golang.org/p/dK4DT0iUfzY

Result:

started goroutine: B
started goroutine: A
about end goroutine: A
about end goroutine: B
17 -5
Akavall
  • 82,592
  • 51
  • 207
  • 251
  • Thank you. This helped. And your answer was fully correct. I accepted the answer from Hamza probably because I read it 2nd, and the light didn't turn on until I'd read his too. But I upvoted yours too. I was actually asking the wrong question. The real question is why the 2nd thread keeps finishing before the 1st one. It seems like at the very least the 1st one should complete more than 1/2 the time before the 2nd one. I don't see why Hamza's snippet is random whereas mine isn't. But, it's very clear that I can't expect any particular order where unbuffered channels are concerned. – mikekehrli Aug 24 '18 at 04:37