In Go what happens if you write to closed channel? Can I treat channels as deterministic RE destruction?

27,403

If you write to a closed channel, your program will panic (see http://play.golang.org/p/KU7MLrFQSx for example). You could potentially catch this error with recover, but being in a situation where you don't know whether the channel you are writing to is open is usually a sign of a bug in the program. The send side of the channel is responsible for closing it, so it should know the current state. If you have multiple goroutines sending on the channel, then they should coordinate in closing the channel (e.g. by using a sync.WaitGroup).

In your Time.DoAfter hypothetical, it would depend on whether the channel was buffered. If it was an unbuffered channel, then the goroutine writing to the timer channel would block until someone read from the channel. If that never happened, then the goroutine would remain blocked until the program completed. If the channel was buffered, the send would complete immediately. The channel could be garbage collected before anyone read from it.

The standard library time.After behaves this way, returning a channel with a one slot buffer.

Share:
27,403
Alec Teal
Author by

Alec Teal

Updated on July 09, 2022

Comments

  • Alec Teal
    Alec Teal almost 2 years

    Okay SO is warning me about a subjective title so please let me explain. Right now I'm looking at Go, I've read the spec, watched a few IO talks, it looks interesting but I have some questions.

    One of my favourite examples was this select statement that listened to a channel that came from "DoAfter()" or something, the channel would send something at a given time from now.

    Something like this (this probably wont work, pseudo-go if anything!)

    to := Time.DoAfter(1000 * Time.MS)
    select:
        case <-to:
            return nil //we timed out
        case d := <-waitingfor:
            return d
    

    Suppose the thing we're waiting for happens really fast, so this function returns and isn't listening to to any more, what happens in DoAfter?

    I like and know that you ought not test the channel, for example

    if(chanToSendTimeOutOn.isOpen()) {
        chanToSendTimeOutOn<-true
    }
    

    I like how channels sync places, with this for example it is possible that the function above could return after the isOpen() test but before the sending of true. I really am against the test, this avoids what channels do - hide locks and whatnot.

    I've read the spec and seen the run time panics and recovery, but in this example where do we recover? Is the thing waiting to send the timeout a go routine or an "object" of sorts? I imagined this "object" which had a sorted list of things it had to send things to after given times, and that it'd just append TimeAfter requests to the queue in the right order and go through it. I'm not sure where that'd get an opportunity to actually recover.

    If it spawned go-routines each with their own timer (managed by the run-time of course, so threads don't actually block for time) what then would get the chance to recover?

    The other part of my question is about the lifetime of channels, I would imagine they're ref counted, well those able to read are ref-counted, so that if nothing anywhere holds a readable reference it is destroyed. I'd call this deterministic. For the "point-to-point" topologies you can form it will be if you stick towards Go's "send stuff via channels, don't access it"

    So here for example, when the thing that wants a timeout returns the to channel is no longer read by anyone. So the go-routine is pointless now, is there a way to make it return without doing work?

    Example:

    File-reading go routine that has used defer to close the file when it is done, can it "sense" the channel it is supposed to send stuff to has closed, and thus return without reading any more?

    I'd also like to know why the select statement is "nondeterministic" I'd have quite liked it if the first case took priority if the first and second are ready (for a non-blocking operation) - I wont condemn it for that, but is there a reason? What's the implementation of this?

    Lastly, how are go-routines scheduled? Does the compiler add some sort of "yielding" every so many instructions, so a thread running will switch between different goroutines? Where can I find info on the lower level stuff?

    I know Go touts that "you simply don't need to worry about this" but I like to know what things I write actually hide (that could be a C++ thing) and the reasons why.

  • bronze man
    bronze man over 6 years
    Why "you don't know whether the channel you are writing to is open is usually a sign of a bug in the program."? Is there any example? I found I just need two channels to solve this channel closed "golang design bug".
  • Zyl
    Zyl over 4 years
    It's quite simple to construct a perfectly plausible scenario where you won't know if the channel is closed or not. E.g. when you have an amount of worker goroutines reporting errors via channels. Whoever reads this error channel might want to close it as soon as they are no longer interested in errors. A typical cause might be context cancellation. This behavior just discourages closing channels and increases likeliness of deadlocking goroutines trying to write to full channels.
  • James Henstridge
    James Henstridge over 4 years
    @Zyl: that simply isn't how Go channels work. If you want the sender to stop producing data, you'll need to create some way to signal them. In the case of the context package, if you don't have access to the context's cancel function, then it probably isn't your responsibility to cancel the context.