A way to detect closed channel

4,861 views
Skip to first unread message

Albert Tedja

unread,
Nov 6, 2017, 7:59:51 PM11/6/17
to golang-nuts
So, I just found out that closed channels always yield the zero value. That means, a closed channel inside a select statement seems to always be guaranteed to be executed.

Since closing an already closed channel creates a panic, does this mean then I can do this to make sure that the channel is closed only once?


select {
   
case <- closedchan:
   
default:
        close
(closedchan)
}



Golang playground: https://play.golang.org/p/LSrTh0HC2K

It seems to work. Just want to confirm here before start doing this everywhere in my code.

Albert Tedja

unread,
Nov 6, 2017, 8:27:04 PM11/6/17
to golang-nuts
Assuming of course, that the channel is only intended to send closed signal to other goroutines, rather than transporting meaningful data.

Dan Kortschak

unread,
Nov 6, 2017, 8:33:31 PM11/6/17
to Albert Tedja, golang-nuts
No. The complete select+code blocks is not atomic, so the select may
see that closedchan is not closed, execute the default case, but in the
mean time closedchan has been closed by someone else. Bang!

Daniel Skinner

unread,
Nov 6, 2017, 8:47:15 PM11/6/17
to Dan Kortschak, Albert Tedja, golang-nuts
meaning, don't call that select+code block from multiple goroutines. If on the other-hand that block is always called from the same goroutine, then it will do what you want, but that may be a slippery slope depending on what you're writing.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Albert Tedja

unread,
Nov 6, 2017, 9:13:00 PM11/6/17
to golang-nuts
That makes sense.  If multiple goroutines are calling that select block, one or more could be attempting to close it.

Okay, nevermind then.

Marvin Stenger

unread,
Nov 6, 2017, 9:19:32 PM11/6/17
to golang-nuts
You might want to use a sync.Once here.
https://golang.org/pkg/sync/#Once

sheepbao

unread,
Nov 6, 2017, 9:39:52 PM11/6/17
to golang-nuts
func isClose() bool {
    select {
    case <-closeChan:
        return true
    default:
        return false
    }
}
    



在 2017年11月7日星期二 UTC+8上午8:59:51,Albert Tedja写道:

Dan Kortschak

unread,
Nov 6, 2017, 10:00:16 PM11/6/17
to sheepbao, golang-nuts
This is not a good idea (and is wrong since it does not check the ok
value of the receive operation). As was pointed out previously, this is
only true in the instant that the select is occurring.

Dave Cheney

unread,
Nov 7, 2017, 1:58:38 AM11/7/17
to golang-nuts

roger peppe

unread,
Nov 7, 2017, 4:32:40 AM11/7/17
to Albert Tedja, golang-nuts
On 7 November 2017 at 00:59, Albert Tedja <nicho...@gmail.com> wrote:
> So, I just found out that closed channels always yield the zero value. That
> means, a closed channel inside a select statement seems to always be
> guaranteed to be executed.
>
> Since closing an already closed channel creates a panic, does this mean then
> I can do this to make sure that the channel is closed only once?
>
>
> select {
> case <- closedchan:
> default:
> close(closedchan)
> }

You can do that if you wrap it in a mutex so that nothing else
can be doing the same thing at the same time. That's
a common workaround for the fact that sometimes you want
to be able to close a signal-only channel (chan struct{}) from
a Close method that might be called multiple times.

Another possibility might be to do this:

func closeChan(c chan struct{}) {
defer func() {
// Catch the panic from closing an already-closed channel.
recover()
}()
close(c)
}

Steven Hartland

unread,
Nov 7, 2017, 4:43:22 AM11/7/17
to golan...@googlegroups.com
If you're using it as a signal to trigger only on close and not sending any data, you should use chan struct{}, the reason for this is is that the empty struct consumes zero storage.

Also there is a multi-valued assignment form of the receive operator, which reports whether a received value was sent before the channel was closed.

Dave Cheney's article curious channels is also relate so worth a read.
   
    Regards
    Steve

Dave Cheney

unread,
Nov 7, 2017, 4:53:50 AM11/7/17
to golang-nuts
gross.

Daniel Skinner

unread,
Nov 7, 2017, 9:47:45 AM11/7/17
to roger peppe, Albert Tedja, golang-nuts
Instead of recover, I'd consider simply writing to the channel.

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

If it has to be a close, pass in additional state to close only once.

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

Jason E. Aten

unread,
Nov 8, 2017, 12:09:57 AM11/8/17
to golang-nuts
As others pointed out, you need to use a lock if you want to close a channel from multiple, possibly concurrent, code locations. 

Here is an example of how I handle such situations:

https://github.com/glycerine/idem/blob/master/halter.go#L11

Feel free to use that library, or parts of it.  In short, if you want an idempotent close, you have to replace close() with your own Close().

Meta comment: it was, in my view, a poor design decision in Go (not to allow multiple closes of a channel). For backwards compatibility reasons we are stuck with it now.  Its a wart/minor annoyance in the grand scheme of things. Fortunately, the workarounds, while elaborate and slow, are very rarely on hot code paths.

Jason E. Aten

unread,
Nov 8, 2017, 12:14:29 AM11/8/17
to golang-nuts
Also since nobody else mentioned it, for completeness: the optional second argument to the receive will always tell you if the channel is already closed, if all you are doing is trying to distinguish the zero value from a closed versus unclosed channel.

Christian Surlykke

unread,
Nov 8, 2017, 3:26:07 AM11/8/17
to golang-nuts
This little pattern may also be useful:

ticket := make(chan bool, 1)
ticket
<- true
close
(ticket)

Now, first read from ticket will yield true, all subsequent reads will yield false.
So any number of go-routines can do this if, but the call to close will happen at most once:

if (<-ticket) {
    close
(myChannel)
}

br. Chr.
Reply all
Reply to author
Forward
0 new messages