Channel read continuously default values

Hi ,

I have a go routine which will read the data from the channel and main routine which will write the data into the channel.

package main

import “time”

func main() {
var dataCh = make(chan int )
go readDataCh(dataCh)

for i:=0 ; i <10 ; i++{
	dataCh <- i
}
close(dataCh)
time.Sleep(10*time.Second)

}
func readDataCh(ints chan int) {
time.Sleep(10time.Second)
for {
select {
case d ,_:= <-ints:
println(":data is there", d)
/
if !ok{
return
}*/
}

 }

}

Question :

  1. Why after reading the data written into channel , it gives continuously default values (for int , 0 and for string , “” )
  2. I have read at one place (https://www.godesignpatterns.com/2014/04/exiting-multiple-goroutines-simultaneously.html ), where they have mentioned that closing the channel will stop the operation exit. But it didn’t do the same. Can you help me the same?
2 Likes

Reading from a closed channel is non-blocking and returns the default value.

d, ok := <- ints

When ints is closed, ok will be false and d will be 0 because it is the default value for integers.
You removed the ok and commented out the test of ok. This is the reason you keep receiving 0. The go routine won’t terminate as it should when ints is closed.

This is what your code should be

package main

import "time"

func main() {
	var dataCh = make(chan int)
	go readDataCh(dataCh)

	for i := 0; i < 10; i++ {
		dataCh <- i
	}
	close(dataCh)
	time.Sleep(10 * time.Second)

}
func readDataCh(ints chan int) {
	time.Sleep(10 * time.Second)
	for {
		select {
		case d, ok := <-ints:
			if !ok {
				return
			}
			println(":data is there", d)
		}
	}
}

It will print :data is there 0 with numbers from 0 to 9. Notice that I swapped the if clause and the println. With my code, nothing will be printed once ok is false. Otherwise it will print one additional :data is there 0 after :data is there 9 because d is 0 when ok is false.

About the exit question, the code you referenced is using a channel for signaling the termination. In this case nothing is ever written to the channel shutdown. The instruction case <-shutdown thus blocks until the shutdown channel is closed. When shutdown is closed, <-shutdown is unblocked and the case clause is triggered. If many go routines are blocked on this <-shutdown, they will all be unblocked and execute the case code.

5 Likes

Thank you for your detailed explanation.

1 Like

Hi,

The reason why a closed channel keeps delivering the same default value over and over is because several consumers could be consuming from the same channel. They have to get a notification of the closing of the channel, and this is done by sending default value with a boolean indicating the state of the channel as Christophe explained. In extreme case, as long as the channel is valid, you can pass it and listen to it in many places. Even long time after it has been closed.
Have a nice day.

1 Like

Hi Ivan, we cant send to closed channel rite ? Can you explain your last point?
It can be situation that data may write into channel at different interval in real time , so how does it will help consumers to notify.

1 Like

Hi,

Sure you can’t write to a closed channel but nothing prevent you to continue to listen as long as you want and whenever you want.The fact is that in a concurrent application when you close a channel, multiple channels can be impacted and they will necessarily be notified in the future (after a current message is processed for example).You don’t have to send to a closed channel for it to produce the default value as a message for closing. It’s a natural behavior of a closed channel. A trivial example could be :

package main

import (
	"fmt"
	"sync"
)

func main() {
	ch := make(chan int)
	close(ch)
	const N = 10
	var wg sync.WaitGroup
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {

			for msg := range ch {
				fmt.Println(msg)
			}
			fmt.Println("exiting")
			wg.Done()
		}()

	}
	wg.Wait()

}

You see that all the goroutines should be notified that the channel is closed to exit. As the system can’t predict the number of listening goroutines, nor how many times you will be listening to the closed channel,nor when you will do that, it has no better solution than keeping sending (or emitting if you prefer) over and over again.

1 Like

Hi Ivan , yes we cannot send the data to the closed channel and then why consumers have to listen to the default value?

Ivan : The fact is that in a concurrent application when you close a channel, multiple channels can be impacted and they will necessarily be notified in the future
Gowtham : - when we close the channel , range will iterate still channel has data and then end the range loop. In this case , how multiple channels (consumers) will get affected when we close the channel

Kindly clarify the same.

1 Like

@Gowtham_Girithar: Your code from your first post has a select within a for loop:

for {
    select {
    case d ,_:= <-ints:
        println(":data is there", d)
        /* if !ok{
            return
        } */
    }
}

but on the site you referenced, it’s just a select:

select {
case <-shutdown:
	done <- i
}

In the example from the site you listed, 5 goroutines are started and all wait for a value to come through on the shutdown channel. If you send values into that channel, one of the goroutines would complete per value sent. If you close the channel, then all of the goroutines immediately return a default value from the shutdown channel and send their id (the i variable) into the done channel.

1 Like

In fact, they’re not listening for default values, they’re listening for values until they get a message notifying of the closing. But try to imagine what could be this notification message ? The most reasonable choice is to send the default value AND a side value indicating that the message is in fact a message of closing. You can’t imagine a value that could alone signify the closing as any value is a valid message. For example, the range over a channel is a syntactic sugar that replaces in my previous example :

for msg, ok := <-ch; ok; {
	fmt.Println(msg)
}

In your second question, you assume that you will use a range. Yes, range takes care nicely and transparently the closing of a channel. But it’s not the sole syntax that you could use or need. Just look at this rewrite of example with for… select :

package main

import (
	"fmt"
	"sync"
)

func main() {
	ch := make(chan int)
	close(ch)
	const N = 10
	var wg sync.WaitGroup
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {
			defer wg.Done()
			defer fmt.Println("exiting")

			for {
				select {
				// Suppose there are other cases here, before the next one
				case msg, ok := <-ch:
					if !ok {
						return
					}
					fmt.Println(msg)

				}
			}

		}()

	}
	wg.Wait()

}

A good example of multiple channel affected by a single closing is the workers pool pattern. Jobs are distributed over a single channel listened by N workers. When the jobs pipeline is closed, the workers exit. Each one of them has to be able to listen to the closing of the very same channel (other design are also possible or even better of course).

2 Likes

Thank you so much Ivan for helping me to understand.
My question is , If I can check whether channel is not closed or not with “ok” and then why do channel have to send the default value - sorry it is not still clear. Because I still feel , message as channel closed or opened is enough for me exit the workers.
whenever case is getting executed, we will check that whether the channel is closed or not with “ok” (like the one you are checking in case statement)

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.