goconcurrencygoroutine

Multiple goroutines listening on one channel


I have multiple goroutines trying to receive on the same channel simultaneously. It seems like the last goroutine that starts receiving on the channel gets the value. Is this somewhere in the language spec or is it undefined behaviour?

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

Output:

goroutine 4

Example On Playground

EDIT:

I just realized that it's more complicated than I thought. The message gets passed around all the goroutines.

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

Output:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

NOTE: the above output is outdated in more recent versions of Go (see comments)

Example On Playground


Solution

  • This behavior is not defined by the language specification. The scheduler — which is an implementation detail — will take care of it. Note that if the work to be done is trivial and/or one of the goroutines hasn't fully started yet, all work might be scheduled to the first available goroutine. Nothing imposes to split the work equally.

    However there are a couple of rules of thumb that should make things feel much more straightforward.

    Here's an alternative version of your program, applying these two guidelines. This case demonstrates many writers & one reader on a channel:

    c := make(chan string)
    
    for i := 1; i <= 5; i++ {
        go func(i int, co chan<- string) {
            for j := 1; j <= 5; j++ {
                co <- fmt.Sprintf("hi from %d.%d", i, j)
            }
        }(i, c)
    }
    
    for i := 1; i <= 25; i++ {
        fmt.Println(<-c)
    }
    

    http://play.golang.org/p/quQn7xePLw

    It creates the five go-routines writing to a single channel, each one writing five times. The main go-routine reads all twenty five messages - you may notice that the order they appear in is often not sequential (i.e. the concurrency is evident).

    This example demonstrates a feature of Go channels: it is possible to have multiple writers sharing one channel; Go will interleave the messages automatically.

    The same applies for one writer and multiple readers on one channel, as seen in the second example here:

    c := make(chan int)
    var w sync.WaitGroup
    w.Add(5)
    
    for i := 1; i <= 5; i++ {
        go func(i int, ci <-chan int) {
            j := 1
            for v := range ci {
                time.Sleep(time.Millisecond)
                fmt.Printf("%d.%d got %d\n", i, j, v)
                j += 1
            }
            w.Done()
        }(i, c)
    }
    
    for i := 1; i <= 25; i++ {
        c <- i
    }
    close(c)
    w.Wait()
    

    This second example includes a wait imposed on the main goroutine, which would otherwise exit promptly and cause the other five goroutines to be terminated early (thanks to olov for this correction).

    In both examples, no buffering was needed. It is generally a good principle to view buffering as a performance enhancer only. If your program does not deadlock without buffers, it won't deadlock with buffers either (but the converse is not always true). So, as another rule of thumb, start without buffering then add it later as needed.