When closing a channel causes panic? Is it necessary to close the channel? How to determine whether a channel is closed? How to gracefully close a channel? Did you know all this? (Don’t tell me you only know how to answer the last question!) See this question, do not know you can not help but sigh, which hell always say go channel “philosophy” “elegant”? Rob Pike (father of go) might say: Well, of course “philosophical” and “elegant”, just a little too much for your attention…

When closing a channel causes panic?

Take a look at an example:

// 1. Close when not initialized
func TestCloseNilChan(t *testing.T) {
   var errCh chan error
   close(errCh)
   
   // Output:
   // panic: close of nil channel
}

// 2. Repeat the shutdown
func TestRepeatClosingChan(t *testing.T) {
   errCh := make(chan error)
   var wg sync.WaitGroup
   wg.Add(1)

   go func(a) {
      defer wg.Done()
      close(errCh)
      close(errCh)
   }()

   wg.Wait()
   
   // Output:
   // panic: close of closed channel
}

// 3. Close the file and send it
func TestSendOnClosingChan(t *testing.T) {
   errCh := make(chan error)
   var wg sync.WaitGroup
   wg.Add(1)

   go func(a) {
      defer wg.Done()
      close(errCh)
      errCh <- errors.New("chan error")
   }()

   wg.Wait()
   
   // Output:
   // panic: send on closed channel
}

// 4. Close when sending
func TestCloseOnSendingToChan(t *testing.T) {
   errCh := make(chan error)
   var wg sync.WaitGroup
   wg.Add(1)

   go func(a) {
      defer wg.Done()
      defer close(errCh)

      go func(a) {
         errCh <- errors.New("chan error") // Since chan has no buffered queue, the code will always block here
      }()

      time.Sleep(time.Second) // Wait for data to be sent to errCh
   }()

   wg.Wait()

   // Output:
   // panic: send on closed channel
}
Copy the code

To sum up, we can summarize the following knowledge points:

Closing a channel can cause panic in one of the following four cases: closing uninitialized, repeating closing, sending after closing, and closing while sending.

In addition, we can see from Golang’s error that golang considers cases 3 and 4 to be one case.

By observing the above code, in order to avoid the problem of repeated closing and sending after closing when using channel, I think we can summarize the following two rules:

  • A channel should be closed only at the sending end. (Prevent sending after closing)
  • Do not close the sender channel when multiple senders exist, but use a dedicated Stop channel. (Because multiple senders are sending, it is not possible to close multiple senders at the same time, otherwise it would cause a duplicate shutdown. The receiver closes the stop channel. If there are multiple pairs, either party will close the Stop channel, and both parties will listen to stop sending and receiving when the stop channel terminates.)

These two rules are known as the channel closure rules.

Since closing a channel is so troublesome, is it necessary to close a channel? What if it doesn’t?

Is it necessary to close the channel? What if it doesn’t?

Let’s consider the following two scenarios:

Case 1: The sending times of a channel are equal to the receiving times

func TestIsCloseChannelNecessary_on_equal(t *testing.T) {
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())
    ich := make(chan int)

    // sender
    go func(a) {
       for i := 0; i < 3; i++ {
          ich <- i
       }
    }()

    // receiver
    go func(a) {
       for i := 0; i < 3; i++ {
          fmt.Println(<-ich)
       }
    }()

    time.Sleep(time.Second)
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())
   
    // Output:
    // NumGoroutine: 2
    / / 0
    / / 1
    / / 2
    // NumGoroutine: 2
}
Copy the code

When the number of channel sending times is equal to the number of channel receiving times, the sender’s Go routine and receiver’s Go routine end their go routine respectively when the sending or receiving ends. The ICH in the above code will be collected by the garbage collector because the code is not used. Therefore, in this case, the channel is not closed, and there are no side effects.

Case 2: The number of sending a channel is greater than or less than the number of receiving a channel

func TestIsCloseChannelNecessary_on_less_sender(t *testing.T) {
   fmt.Println("NumGoroutine:", runtime.NumGoroutine())
   ich := make(chan int)

   // sender
   go func(a) {
      for i := 0; i < 2; i++ {
         ich <- i
      }
   }()

   // receiver
   go func(a) {
      for i := 0; i < 3; i++ {
         fmt.Println(<-ich)
      }
   }()

   time.Sleep(time.Second)
   fmt.Println("NumGoroutine:", runtime.NumGoroutine())
   
   // Output:
   // NumGoroutine: 2
   / / 0
   / / 1
   // NumGoroutine: 3
}
Copy the code

Take the above code as an example. When the number of channel sending times is less than the number of channel receiving times, the receiver’s Go routine is blocked because of waiting for the sender to send messages. Therefore, the recipient’s Go routine does not exit, and ICH cannot be garbage collected because it is used by the recipient. Unexited Go routines and unreclaimed channels both cause memory leaks.

Therefore, in a one-to-one situation between sender and receiver, it is possible not to close a channel as long as we ensure that the sender or receiver does not block. When we cannot accurately determine the number of times a channel is sent or received, we should close the channel at the appropriate time. How do you tell if a channel is closed?

How to determine whether a channel is closed?

When a Go channel is closed, reading the channel will never block, and only the corresponding type of zero will be output.

The following code looks like this:

func TestReadFromClosedChan(t *testing.T) {
   var errCh = make(chan error)

   go func(a) {
      defer close(errCh)
      errCh <- errors.New("chan error")
   }()

   go func(a) {
      for i := 0; i < 3; i++ {
         fmt.Println(i, <-errCh)
      }
   }()

   time.Sleep(time.Second)
   
   // Output:
   // 0 chan error
   // 1 <nil>
   // 2 <nil>
}
Copy the code

In the above code example, nil might also be one of the values that needs to be transmitted by a channel, and there is usually no way to determine whether a channel is closed by determining whether it is a zero value of type. So to avoid output of meaningless values, we need a reasonable way to determine whether a channel is closed. Golang officially offers us two ways.

Solution 1: Use multiple return values for channel (err, OK := < -errch)

func TestReadFromClosedChan2(t *testing.T) {
   var errCh = make(chan error)
   go func(a) {
      defer close(errCh)
      errCh <- errors.New("chan error")
   }()

   go func(a) {
      for i := 0; i < 3; i++ {
         if err, ok := <-errCh; ok {
            fmt.Println(i, err)
         }
      }
   }()

   time.Sleep(time.Second)
   
   // Output:
   // 0 chan error
}
Copy the code

Err, ok := <-errCh the second return value ok indicates whether errCh has been turned off. Returns true if closed.

Solution 2: Use for range to simplify the syntax

func TestReadFromClosedChan(t *testing.T) {
   var errCh = make(chan error)
   go func(a) {
      defer close(errCh)
      errCh <- errors.New("chan error")
   }()

   go func(a) {
      i := 0
      for err := range errCh {
         fmt.Println(i, err)
         i++
      }
   }()

   time.Sleep(time.Second)
   
   // Output:
   // 0 chan error
}
Copy the code

The for range syntax automatically determines whether a channel ends, and if it does, it exits the for loop.

How to gracefully close a channel?

As we have learned from the previous article, if repeated shutdown and sending after shutdown occurs, channel panic will occur. So how to gracefully close the channel is a problem we care about.

Golang officially offers us a way to avoid this problem as much as possible. Golang allows us to use <- to control the direction in which a channel is sent, preventing us from closing it at the wrong time.

func TestOneSenderOneReceiver(t *testing.T) {
   ich := make(chan int)
   go sender(ich)
   go receiver(ich)
}

func sender(ich chan<- int) { 
   for i := 0; i < 100; i++ {
      ich <- i
   }
}

func receiver(ich <-chan int) { 
   fmt.Println(<-ich)
   close(ich) // Here the code will report an error at compile time
}
Copy the code

With this approach, since the close() function can only accept channels of type chan< -t, the compiler will report an error if we try to close a channel on the receiver, so we can detect the error ahead of compile time.

In addition, we can also use the following structure (from Go101: How to Gracefully Close Go Channels with some modifications) :

type Channel struct {
   C      chan interface{}
   closed bool
   mut    sync.Mutex
}

func NewChannel(a) *Channel {
   return NewChannelSize(0)}func NewChannelSize(size int) *Channel {
   return &Channel{
      C:      make(chan interface{}, size),
      closed: false,
      mut:    sync.Mutex{},
   }
}

func (c *Channel) Close(a) {
   c.mut.Lock()
   defer c.mut.Unlock()
   if! c.closed {close(c.C)
      c.closed = true}}func (c *Channel) IsClosed(a) bool {
   c.mut.Lock()
   defer c.mut.Unlock()
   return c.closed
}

func TestChannel(t *testing.T) {
   ch := NewChannel()
   println(ch.IsClosed())
   ch.Close()
   ch.Close()
   println(ch.IsClosed())
}
Copy the code

This solution can solve the problem of repeatedly closing the lock and whether the lock is closed. Use channel.isclosed () to determine whether a Channel IsClosed and can safely be sent and received. We could also change sync.Mutex to sync.Once to close the channel only Once. For details, see How to Gracefully Turn Off Go Channels.

Sometimes our code already uses native Chan, or we don’t want to use a separate data structure, we can also use the following alternatives. In general, there are only four situations where you need to close a channel (here’s my summary of how to Gracefully Close Go Channels) :

  • One sender, one receiver: The sender closes the channel, and the receiver uses select or for range to determine whether the channel is closed.
  • One sender, multiple receivers: the sender closes a channel, ditto.
  • Multiple senders and one receiver: After receiving the receiver, close the receiver with a dedicated Stop channel. The sender uses SELECT to listen for the stop channel to be closed.
  • Multiple senders, multiple receivers: either party closes with a dedicated Stop channel; Both sender and receiver use SELECT to listen for the stop channel to be closed.

So we just need to remember how to close the channel when faced with these four situations. In order to avoid pure plagiarism, the specific code implementation can refer to the article “How to Gracefully Close Channels” (click in the middle and look for the keyword “Elegant solution to maintain Channel Closing principle”).

An overview

Code doesn’t lie. As it turns out, there are plenty of issues to be aware of when using Go Channel. Novice users can cause all kinds of problems if they are not careful. Even experienced users can’t avoid memory leaks when using The Go Channel. I’ll write a follow-up article to discuss the memory leaks that Go Channel can cause in more detail. But that doesn’t matter. The important thing is: like, bookmark and follow!