go channel 的应用可以说满是知识点,算是 golang 中的一个难点。新手使用时只要稍一不谨慎,就会造成各种问题。比如阻塞、panic、内存泄漏。接下来我将通过代码详细阐述这些问题及其解决方案。
目录
- channel 为什么阻塞了?
- 什么情况下关闭 channel 会造成 panic ?
- 有没有必要关闭 channel?不关闭又如何?
- 如何判断 channel 是否关闭?
- 如何优雅地关闭 channel ?
channel 为什么阻塞了?
【知识点】go channel 如果没有设置缓冲队列,无论读取还是写入,都会阻塞。
如下代码所示:
func TestBlocking(t *testing.T) {
errCh := make(chan error) // 1
fmt.Println("make(chan error)")
errCh <- errors.New("chan error") // 2
fmt.Println("finish", <-errCh)
// Output:
// make(chan error)
}
上述代码会一直阻塞。因为 1 处创建了一个无缓存队列的 channel,所以代码一直阻塞在 2 处。一种解决方案是创建 channel 时使用缓冲队列(如将 1 处代码替换为 errCh := make(chan error, 1)
);一种是使用 go routine 进行发送或读取操作,以防止阻塞(如下代码所示)。
func TestWithoutBlocking(t *testing.T) {
errCh := make(chan error)
fmt.Println("make(chan error)")
go func() {
errCh <- errors.New("chan error") }
fmt.Println("finish", <-errCh)
}
什么情况下关闭 channel 会造成 panic ?
先看示例:
// 1.未初始化时关闭
func TestCloseNilChan(t *testing.T) {
var errCh chan error
close(errCh)
// Output:
// panic: close of nil channel
}
// 2.重复关闭
func TestRepeatClosingChan(t *testing.T) {
errCh := make(chan error)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
close(errCh)
close(errCh)
}()
wg.Wait()
// Output:
// panic: close of closed channel
}
// 3.关闭后发送
func TestSendOnClosingChan(t *testing.T) {
errCh := make(chan error)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
close(errCh)