Multiple go routines consuming from a channel causing loss of data

2.6k Views Asked by At

I am a newbie to Go. In my example below, multiple go routines are consuming from an unbuffered channel.

Code :

var c = make(chan int)

func f() {
    for val := range c {    
        fmt.Printf("routine 1 : %v\n", val)
    }
}   

func g() {
    fmt.Printf("routine 2 : %v\n", <-c)
}

func main() {
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c) 
}

The output was :

routine 1 : 0
routine 1 : 2
routine 2 : 1
routine 1 : 3
routine 1 : 4

Value 5 is missing from this and never gets printed ! Why is this happening? If I remove the call - go g(), it works perfectly.

Also, if I make the channel buffered, say :

var c = make(chan int, 10)

There is no output at all. I understand that for unbuffered channel, the send completes after receive completes, which is not the case for buffered. Still, for buffered case, if channel has not yet sent any int, wouldn't the for loop be blocked considering it a nil channel?

Please help out with both my queries. Appreciate all the inputs.

3

There are 3 best solutions below

0
On

As soon as 5 is consumed, the program exits. There's no time for it to print the output. If you run the program enough times, you may find that on some occasions, it does happen to print the output before it closes, but it'll be purely random.

You need to add some mechanism to wait for your channels to finish, before exiting the program.

0
On

What @Flimzy mentions is correct, after the read is over the send unblocks and the main go routine exits after closing the channel even before the print could complete (sometimes it may complete so you may see it once in a while). Once the main go routine exits, so do all other go routines. Here's a solution which uses WaitGroup for synchronization https://play.golang.org/p/i2uHw3X1G3 - with this you should not see any missouts. Hope this helps.

For buffered channel, the main go routine doesn't have to wait for the read to complete after writing the values, so it just exits after it sends all the values. So again, you need to use synchronization for this.

7
On

You need to wait for goroutines to finish:


method 1: using sync.WaitGroup:

// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.

try this:

package main

import (
    "fmt"
    "sync"
)

var c = make(chan int)
var wg sync.WaitGroup

func f() {
    defer wg.Done()
    for val := range c {
        fmt.Printf("routine 1 : %v\n", val)
    }
}

func g() {
    defer wg.Done()
    if data, ok := <-c; ok {
        fmt.Println("routine 2 :", data)
    }
}

func main() {
    wg.Add(2)
    defer wg.Wait()
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c)
}

sample output:

routine 2 : 0
routine 1 : 1
routine 1 : 2
routine 1 : 3
routine 1 : 4
routine 1 : 5

method 2: using quit channel, try this:

package main

import "fmt"

func main() {
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c)
    <-quit
    <-quit
}
func f() {
    defer done()
    for val := range c {
        fmt.Printf("routine 1 : %v\n", val)
    }
}
func g() {
    defer done()
    if data, ok := <-c; ok {
        fmt.Println("routine 2 :", data)
    }
}
func done() {
    quit <- struct{}{}
}

var c = make(chan int)
var quit = make(chan struct{}, 2)

output:

routine 2 : 0
routine 1 : 1
routine 1 : 2
routine 1 : 3
routine 1 : 4
routine 1 : 5