Go: feature request: Go 2: isClosed(channelName)

Created on 18 Mar 2019  路  4Comments  路  Source: golang/go

What version of Go are you using (go version)?

$ go version go1.12.1 windows/amd64

What did you do?

package main

import "fmt"

func main() {

    messageCh := make(chan string)

    go func() { messageCh <- "ping" }()

    msg, ok := <-messageCh
    fmt.Println(msg, ok)

    //close(messageCh) would close chan
    //close(messageCh) would cause error since it is already closed

    select {
    case <-messageCh:
        fmt.Println("already closed")
    default:
        fmt.Println("safe to close, closing")
        close(messageCh)
    }

    //close(messageCh) would cause error

    select {
    case <-messageCh:
        fmt.Println("already closed")
    default:
        fmt.Println("safe to close, closing")
        close(messageCh)
    }
}

What did you expect to see?

I might be missing something since I'm new to Go, but if one can test the length of a channel by length(messageCh), then shouldn't it be a similar way to test if channel is closed like: IsClosed(messageCh)?
If such possibility does exist, then it should somehow be better presented in documentation, since I've seen all sorts of several line recipes for what should be a simple test.

FeatureRequest FrozenDueToAge Go2 LanguageChange

Most helpful comment

One problem with isClosed is that it is always racy. The same is true of len applied to a channel, of course. But at least len of a channel has a use, in that it gives you a snapshot of how much the channel is being used, which could be useful for stats reporting, even if the value might change before it is reported. But isClosed has no such use. The only reason to call isClosed would be to make some decision based on whether the channel is closed, but of course the channel might be closed before you actually test the result of isClosed.

Another problem with isClosed is that it misunderstands the point of closing a channel. Closing a channel is a send operation that can be seen by reading from the channel. Only the sender should close a channel, when there is nothing more to send. Only the reader should check whether a channel is closed. And since the reader is checking whether the channel is closed, it does not harm to combine the operations of reading from the channel and checking whether it is closed. And combining those operations avoids the race condition.

All 4 comments

One problem with isClosed is that it is always racy. The same is true of len applied to a channel, of course. But at least len of a channel has a use, in that it gives you a snapshot of how much the channel is being used, which could be useful for stats reporting, even if the value might change before it is reported. But isClosed has no such use. The only reason to call isClosed would be to make some decision based on whether the channel is closed, but of course the channel might be closed before you actually test the result of isClosed.

Another problem with isClosed is that it misunderstands the point of closing a channel. Closing a channel is a send operation that can be seen by reading from the channel. Only the sender should close a channel, when there is nothing more to send. Only the reader should check whether a channel is closed. And since the reader is checking whether the channel is closed, it does not harm to combine the operations of reading from the channel and checking whether it is closed. And combining those operations avoids the race condition.

Thank you for your answer. I understand your point. But I do see places where isClosed() can be used.
For example this part of code. Yes, we can argue this can be done in multiple ways, but I've been burnt by this code pattern with done() multiple times (I admit I'm new to Go), and yet why? If you can simply introduce _non-panic-if-already-closed_ close(), or isClose()

package main

import (
    "fmt"
    "time"
)

var messages chan string
var signals chan bool
var done chan struct{}

func main() {

    messages = make(chan string)
    signals = make(chan bool)
    done = make(chan struct{})

    go work()
    go work()

    messages <- "hello"
    signals <- true
    time.Sleep(time.Second)

    //we are happy, we would like to exit program
    WorkersDone()

    //but we cannot, chan was closed while we were not paying attention
    //and this line never happens
    fmt.Println("exit")
}

//we use this to call this function to broadcast to stop execution of tone of workers
func WorkersDone() {

    /*but to check if chan is closed, we need to do this
    select {
    case <-done:
        fmt.Println("already closed")
    default:
        fmt.Println("safe to close, closing")
        close(done)
    }*/


    //because if are naive enough to just close, this would crash
    close(done)
    //what if there was isClosed(done) or closeIfOpen(done) or something...
}

func work() {
    for {
        select {
        case msg := <-messages:
            fmt.Println("received message", msg)
        case sig := <-signals:
            fmt.Println("received signal", sig)

            /*
                say we do some math, whatever, but then some error happens,
                or we find result we need... and so we want to exit workers
            */

            WorkersDone()

        case <-done:
            fmt.Println("closing")
            return
        }
    }
}

Thank you.

I note that isClosed won't help your example program. You could write workersDone to say

    if !isClosed(done) {
        close(done)
    }

but if two different goroutines call workersDone simultaneously, as appears possible above, then they could both call isClosed simultaneously, both see that the channel is closed, then both call close simultaneously, at which point one of the goroutines will panic. This is the kind of thing I mean when I say that isClosed is racy.

That said, to shut down a group of workers, use a context.Context. See https://blog.golang.org/context .

More generally, yes, I agree that there are times when it can be useful to use a channel that can be closed from multiple points in the program. Those times arise when you aren't actually sending a value on the channel, but are using it purely as a signal. And for those times it's not too hard to write a little type with a sync.Once and a channel, so that close is only called once on the channel. Adding an isClosed function is not the answer.

As discussed above this construct is inherently racy. We aren't going to adopt it.

Was this page helpful?
0 / 5 - 0 ratings