How one can deterime the order that the order of receiving from channels?

Issue

Consider the following example from the tour of go.

How one can determine the order of reception from the channels?
why x always get the first output from the gorouting?
It sounds reasonable but i didn’t find any documentation about it.
I tried to add some sleep and still x get the input from the first executed gorouting.

    c := make(chan int)
    go sumSleep(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)

The sleep is before the sending to the channel.

Solution

To add a bit to Adrian’s answer: we don’t know in what order the two goroutines might run. If your sleep comes before your channel-send, and sleeps “long enough”,1 that will guarantee that the other goroutine can run to the point of doing its send. If both goroutines run “at the same time” and neither waits (as in the original Tour example), we cannot be sure which one will actually reach its c <- sum line first.

Running the Tour’s example on the Go Playground (either directly, or through the Tour website), I actually get:

-5 17 12

in the output window, which (because we know that the -9 is in the 2nd half of the slice) tells us that the second goroutine “got there” (to the channel-send) first. In some sense, that’s just luckā€”but when using the Go Playground, all jobs are run in a fairly deterministic environment, with a single CPU and with cooperative scheduling, so that the results are more predictable. In other words, if the second goroutine got there first on one run, it probably will on the next. If the playground used multiple CPUs and/or a less-deterministic environment, the results might change from one run to the next, but there is no guarantee of that.

In any case, assuming your code does what you say (and I believe it does), this:

go sumSleep(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)

has the first sender wait and the second sender run first. But that’s what we already observed actually happened when we let the two routines race. To see a change, we’d need to make the second sender delay.

I made a modified version of the example in the Go Playground here that prints more annotations. With the delay inserted in the 2nd half sum, we see the first-half sum as x:

2nd half: sleeping for 1s
1st half: sleeping for 0s
1st half: sending 17
2nd half: sending -5
17 -5 12

as we can expect since one second is “long enough”.


1How long is “long enough”? Well, that depends: how fast are our computers? How much other stuff are they doing? If the computer is fast enough, a delay of a few milliseconds, or even a few nanoseconds, may be enough. If our computer is a really old one or very busy with other higher priority tasks, a few milliseconds might not be enough time. If the problem is sufficiently big, one second might not be enough time. It’s often unwise to choose some particular amount of time, if you can control this better by some sort of synchronization operation, and usually you can. For instance, using a sync.WaitGroup variable allows you to wait for n goroutines (for some runtime value of n) to call a Done function before your own goroutine proceeds.


Playground code, copied to StackOverflow for convenience

package main

import (
    "fmt"
    "time"
)

func sum(s []int, c chan int, printme string, delay time.Duration) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    fmt.Printf("%s: sleeping for %v\n", printme, delay)
    time.Sleep(delay)
    fmt.Printf("%s: sending %d\n", printme, sum)
    c <- sum
}

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

    c := make(chan int)
    go sum(s[:len(s)/2], c, "1st half", 0*time.Second)
    go sum(s[len(s)/2:], c, "2nd half", 1*time.Second)
    x, y := <-c, <-c

    fmt.Println(x, y, x+y)
}

Answered By – torek

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.