channel关闭后出现写入问题

channel关闭后出现写入问题

本文代码基于golang 1.19进行讲解(水平有限,共同探讨,不足之处欢迎指正)

今天面试遇到一道channel相关的问题,感觉自己一下在脑袋空白了,什么思路都没有想起来,特此记录一下。

问题:假设有一个Web Server,使用到了channel,并且在一个函数内传递了这个channel,进行往channel中发送数据的操作,并且在函数外部关闭了channel,如何知道channel关闭了,并且使函数内往channel操作不panic。具体表现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {

	ch := make(chan int)

	go func(chan<- int) {
		i := 0
		for {
			i++
			ch <- i
		}
	}(ch)

	time.Sleep(2)
	close(ch)
	time.Sleep(5)
}

// 运行结果
panic: send on closed channel

以上可以看出,当往关闭的channel继续发送数据会出现panic,所以该怎么解决呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {

	ch := make(chan int)

	go func(chan<- int) {
		i := 0
		for {
			i++
			select {
			case ch <- i:
			default:
				// channel 已满或被关闭(无法写入)
				fmt.Println("channel 不可用")
				return
			}
		}
	}(ch)

	time.Sleep(2)
	close(ch)
	time.Sleep(5)
}

// 执行结果
channel 不可用

这种方式无法明确知道 channel 是“已关闭”还是“满了”,只能判断是否可以立即写入。而且会出现,channel未关闭,但是函数直接返回的情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func main() {

	ctx, cancel := context.WithCancel(context.Background())
	ch := make(chan int)

	go func(chan<- int) {
		i := 0
		for {
			i++
			select {
			case <-ctx.Done():
				fmt.Println("收到取消信号,停止写入")
				return
			case ch <- i:
			}
		}
	}(ch)

	time.Sleep(2)
	cancel()
	time.Sleep(1)
	close(ch)
	time.Sleep(5)
}

// 执行结果
收到取消信号停止写入

这样你就不会冒险向一个关闭的 channel 写入数据,你也可以把context换成done channel,效果是一样的(这样我感觉会不会还有坑,希望各位大佬指正)

Go 中 关闭一个 channel 后再次向其发送数据会触发 panic 。因此最佳实践是:

  • 只由发送者关闭 channel (生产者负责关闭)
  • 接收者不关闭 channel
  • 如果你在多个 goroutine 中写入同一个 channel,那么不能随意关闭它,否则可能导致 panic

只要记住:

don’t close a channel from the receiver side and don’t close a channel if the channel has multiple concurrent senders.

更本质的原则:

don’t close (or send values to) closed channels.

https://golang.design/go-questions/channel/graceful-close/