Your program is most certainly non-deterministic, and you will sometimes
close a closed channel and panic. There is no thread safe way to test if a
channel is closed, then close it if it isn't.

I think of channel ownership in a similar way as of memory ownership in
C/C++. If this channel is being passed around to various places, they don't
close it, some outer scope is responsible for that, the producers and
consumers simply read and write to it.

You do have some safe ways of determining if a channel is closed on the
consumer side:
1) val, ok <- c
In this case, reading from a close channel will return the zero value for
"val" and "ok" will be false.
2) for val := range c
In this case, you will iterate over channel messages until it's closed,
then the loop will exit.

On the writer end, it's more difficult. Writing to a closed channel
deadlocks the writer. Using any of the read constructs above to see if it's
closed potentially pulls a legitimate item from the channel, and you can't
put it back without risking the same deadlock if someone downstream closes
it.

So, separate channel ownership from channel use. Just as an example, I
frequently do something like:
func RunProducerConsumerTask()
  c := make(chan...)
  wg := sync.WaitGroup
  for i := 0; i <= 10; i++ {
     wg.Add(1)
     go func() {
        // do some producer stuff on c
       c <- stuff
       // now were' done.
       wg.Done()
  }
wg.Wait()
close(c)
}

This pattern ensures that the outer function creates and closes the channel
safely, nobody else does. The waitgroup makes sure all my producers are
done before closing the channel, at which point it's safe to close, since
the receivers will still drain what's left and then exit safely if they do
checked reads from the channel or iterate over its messages with a loop.

IMO, the language won't always do what you want, so find a safe way of
doing it by changing your approach :)

-- Marcin


On Wed, Mar 13, 2019 at 12:07 AM Andrei Tudor Călin <m...@acln.ro> wrote:

> Hello.
>
> I apologize if my previous mail was cut unexpectedly short. My e-mail
> client
> did something that I do not completely understand.
>
> ISTM that there are no guarantees about the behavior of that program at
> all.
> Access to c from niller and closer is not synchronized, so it's not really
> possible to reason about the behavior of the program.
>
> Superficially, perhaps synchronizing between close(c) and c = nil could fix
> the data race and allow you to reason about the behavior of the program,
> but
> it doesn't feel like that is the real solution. I would look for a way to
> establish a stronger ownership model of the channel in my program, such
> that
> it is obvious which goroutine owns it, closes it, sets it to nil, etc.
>
> On 3/13/19 7:40 AM, Andrey Tcherepanov wrote:
> > Hello fellow Go devs,
> >
> > I have a question that probably is a bit weird and obvious, but here we
> go
> >
> > package main
> >
> > var c chan int
> >
> > func niller() {
> > c = nil
> > }
> >
> > func closer() {
> > close(c)
> > }
> >
> > func main() {
> >
> > c = make(chan int, 1)
> >
> >
> > go closer()
> > go niller()
> >
> > // checkpoint-1
> > if c != nil {
> > // checkpoint-2
> > close(c)
> > c = nil
> > }
> > }
> >
> > What are the guarantees (if any) that c, being non-nil at checkpoint-1
> will
> > not become a nil at checkpoint-2?
> >
> > My take on it that there are none, and the code needs to be fully synced
> > around that close/nil.
> > But ... Is there any hard math theory around how `close()` MUST be
> > implemented in order to have some guarantees about concurrency and
> > consistency?
> >
> > (heavy IMHO warning)
> > Current implementation of close() starts with 2 checks and panics, and
> the
> > more I think of it, the less I am thrilled about both of them. They both
> > causing me nothing but headache by pretty much requiring 2 channels
> > everywhere code that could be much simpler with just 1 channel and no
> > fluff. Implementing this "fluff" is error prone and I would add it is
> not a
> > junior dev task, on whom Go seems to be (quite controversially IMHO)  is
> > focused on.
> >
> > So... Did anybody ever proposed a second close() variant that returns an
> > error instead of hard panic-ing inside?
> >
> > For example, if I have
> >
> > err := close(c)
> >
> > it will not panic, but if I use just
> >
> > close(c)
> >
> > all bets are off, just like in a current Go code? I think this would be
> > perfectly code-compatible with an "old" code, keeping Go1 compatibility
> > guarantee untouched.
> >
> > Thank you very much,
> >    Andrey
> >
>
> --
> Andrei Tudor Călin
>
> --
> 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+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to