How to safely interact with channels in goroutines in Golang

Issue

I am new to go and I am trying to understand the way channels in goroutines work. To my understanding, the keyword range could be used to iterate over a the values of the channel up until the channel is closed or the buffer runs out; hence, a for range c will repeatedly loops until the buffer runs out.

I have the following simple function that adds value to a channel:

func main() {

    c := make(chan int)
    go printchannel(c)
    for i:=0; i<10 ; i++ {
        c <- i
    }

}

I have two implementations of printchannel and I am not sure why the behaviour is different.

Implementation 1:

func printchannel(c chan int) {
    for range c {
        fmt.Println(<-c)
    }
}

output: 1 3 5 7

Implementation 2:

func printchannel(c chan int) {
    for i:=range c {
        fmt.Println(i)
    }
}

output: 0 1 2 3 4 5 6 7 8

And I was expecting neither of those outputs!

Wanted output: 0 1 2 3 4 5 6 7 8 9

Shouldnt the main function and the printchannel function run on two threads in parallel, one adding values to the channel and the other reading the values up until the channel is closed? I might be missing some fundamental go/thread concept here and pointers to that would be helpful.

Feedback on this (and my understanding to channels manipulation in goroutines) is greatly appreciated!

Solution

Implementation 1. You’re reading from the channel twice – range c and <-c are both reading from the channel.

Implementation 2. That’s the correct approach. The reason you might not see 9 printed is that two goroutines might run in parallel threads. In that case it might go like this:

  1. main goroutine sends 9 to the channel and blocks until it’s read
  2. second goroutine receives 9 from the channel
  3. main goroutine unblocks and exits. That terminates whole program which doesn’t give second goroutine a chance to print 9

In case like that you have to synchronize your goroutines. For example, like so

func printchannel(c chan int, wg *sync.WaitGroup) {
    for i:=range c {
        fmt.Println(i)
    }

    wg.Done() //notify that we're done here
}

func main() {
    c := make(chan int)
    wg := sync.WaitGroup{}

    wg.Add(1) //increase by one to wait for one goroutine to finish
              //very important to do it here and not in the goroutine
              //otherwise you get race condition

    go printchannel(c, &wg) //very important to pass wg by reference
                            //sync.WaitGroup is a structure, passing it
                            //by value would produce incorrect results

    for i:=0; i<10 ; i++ {
        c <- i
    }

    close(c)  //close the channel to terminate the range loop
    wg.Wait() //wait for the goroutine to finish
}

As to goroutines vs threads. You shouldn’t confuse them and probably should understand the difference between them. Goroutines are green threads. There’re countless blog posts, lectures and stackoverflow answers on that topic.

Answered By – creker

Answer Checked By – Mildred Charles (GoLangFix Admin)

Leave a Reply

Your email address will not be published.