37

In Go, if a channel channel is closed, I can still read from it using the following syntax and I can test ok to see if it's closed.

value, ok := <- channel
if !ok {
    // channel was closed and drained
}

However, if I don't know whether a channel is closed and blindly write to it, I may got an error. I want to know if there is any way that I can test the channel and only write to it when it's not closed. I ask this question is because sometimes I don't know if a channel is closed or not in a goroutine.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Just a learner
  • 26,690
  • 50
  • 155
  • 234
  • 3
    if you don't know whether a channel is closed or not and blindly write to it, then you have a badly designed program. Redesign it so that there is no way to write into it after it is closed. – ain Aug 29 '16 at 18:57
  • Yes @ain, I completely agree with you! I just want to know, technically, if there is a way to do that. – Just a learner Aug 29 '16 at 18:59
  • Related / possible duplicate: [Closing channel of unknown length](http://stackoverflow.com/questions/34283255/closing-channel-of-unknown-length) – icza Aug 30 '16 at 07:41
  • Does this answer your question? [How to check a channel is closed or not without reading it?](https://stackoverflow.com/questions/16105325/how-to-check-a-channel-is-closed-or-not-without-reading-it) – Kaibo Nov 05 '19 at 08:54

4 Answers4

33

You can't. The rule of thumb here is that only writers should close channels, this way you know that you shouldn't write to that channel anymore.

Some simple code would look like this:

for i := 0; i < 100; i++ {
    value := calculateSomeValue()
    channel <- value
}

close(channel) //indicate that we will no more send values
serejja
  • 22,901
  • 6
  • 64
  • 72
  • 13
    What if multiple goroutines write to the same channel? If one goroutine has done its work it can't simply close the channel. Other goroutines may still need to write to the channel. – Just a learner Aug 29 '16 at 19:03
  • 7
    @OgrishMan: then you have an outside observer close the channel when all goroutines are done, or don't close it at all. – JimB Aug 29 '16 at 19:06
  • 3
    @OgrishMan See a solution to this using `sync.WaitGroup` in this answer: [Closing channel of unknown length](http://stackoverflow.com/a/34283635/1705598). – icza Aug 30 '16 at 07:40
12

If few goroutins write to channel you can also nil it instead of close and use select to read and write. Something like this

ch := make(chan int, 1)
var value int
ch <- 5
select {
case value = <-ch:
    fmt.Println("value", value)
default:
    fmt.Println("oops")
}
ch = nil
select {
case ch <- 5:
default:
    fmt.Println("don't panic")
}
select {
case value = <-ch:
    fmt.Println("value", value)
default:
    fmt.Println("oops")
}

Try it works https://play.golang.org/p/sp8jk961TB

Uvelichitel
  • 8,220
  • 1
  • 19
  • 36
  • Does default mean that there is no data currently or the channel is closed for further reading? – redpix_ Mar 31 '17 at 17:14
  • Here is some code to answer your question: ` close(ch) select { case value, ok := <-ch: fmt.Println("value - ", value) fmt.Println("ok - ", ok) default: fmt.Println("channel closed") }` – SammyRNYCreal Jun 02 '17 at 11:25
  • It will fall through to `default`, like a `switch` statement. I added the following code to test if the channel is closed. The ok var will be false if the channel is closed and value will be 0, which is default nil value for an int. Here is some code to answer your question: ` close(ch) select { case value, ok := <-ch: fmt.Println("value - ", value) fmt.Println("Channel Open - ", ok) default: fmt.Println("oops") } ` – SammyRNYCreal Jun 02 '17 at 11:43
  • 3
    There is a race condition doing this if you write to the channel from multiple go-routines. You need to protect access to the channel with a lock or use an atomic pointer. – Andrew W. Phillips Nov 04 '18 at 22:26
  • it panics when trying to write to a closed channel even with `select`, see this code https://play.golang.org/p/Q4m4_CJl-sJ – Mobigital Apr 10 '19 at 16:03
2

You can't. You could split up the work where messages are sent to a channel and another go routine that reads from a channel.

You should could add a donechannel to signal when the reader is done.

Example.

package main

import (
    "fmt"
)

func main() {

  mychan := make(chan int)
  donechannel := make(chan struct{})
  go pushchannel(mychan)
  go drainchan(mychan, donechannel)
  _, ok := <-donechannel; if !ok {
     fmt.Println("can not read from donechannel this means donechannel is closed, means we are done :)")
  }
  fmt.Println("Done")
}

func pushchannel(ch chan int) {
 fmt.Println("pushing to chan")
 for i:=0; i<=10; i++ {
     fmt.Printf("pushed %v\n",i)
     ch<-i
 }
 close(ch)
}

func drainchan(ch chan int, donechannel chan struct{}) {
  fmt.Println("draining")
  for {
    res, ok := <- ch
    if !ok {
       fmt.Println("result channel is closed, we can signal the donechannel now.")
        close(donechannel)
       break 
    } else {
      fmt.Printf("got result %v\n", res)
    }
  }
}

https://play.golang.org/p/BMyMkrqWF7s

Hace
  • 1,421
  • 12
  • 17
1

Today, I encountered the same problem as you. In my code, the receiver may not read the value from the channel, but the sender will send the value to the channel. Since there is no receiver, the sender will always be blocked. The solution is to use select:

sendVal := "Hello World"

select {

case ch <- sendVal:

case <- time.After(time.Millisecond):

}

If there is no receiver, the above code will directly execute the case <- time.After(time.Millisecond) branch, and then close the channel.

  • same issue here... The challenge is that you're using a timeout and [a] you can never be sure that you have the right timeout that does not create other side effects [b] and it can still produce a panic when writing to a closed channel... and plenty more. – Richard Dec 30 '22 at 15:51