channel 关闭与遍历
本教程共 80 篇 · 第 59 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
59. channel 关闭与遍历
本节目标:学会用
close关闭 channel,用range遍历 channel,搞懂关闭原则和常见错误。
close 函数
close(ch) 关闭一个 channel。关闭后的 channel 有这些特性:
- 不能再发送数据,发送会 panic
- 可以继续接收,先把缓冲区剩余数据收完
- 缓冲区空了之后,接收返回零值,第二个返回值
ok为false
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
v, ok := <-ch // 0, false
fmt.Println(v, ok)
ok 为 false 就是告诉接收方:「channel 关了,数据也没了,别等了」。
range 遍历 channel
for range 可以不断从 channel 接收数据,直到 channel 被关闭:
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 不关闭,下面 range 会死锁
}()
for v := range ch {
fmt.Println(v)
}
}
输出 0 1 2 3 4,然后 range 自动退出。
Warning如果发送方不
close,range会一直等下一个数据。等所有 goroutine 都没数据可发了,就死锁报错:fatal error: all goroutines are asleep - deadlock!
range 只有一个返回值
遍历普通切片时 range 有索引和值两个返回值,但遍历 channel 只有一个——就是接收到的值:
for v := range ch {
fmt.Println(v)
}
因为 channel 没有索引概念,数据是按顺序流出的。
关闭原则:谁发送谁关闭
最重要的原则:只有发送方应该关闭 channel,接收方不要关。
为什么?因为发送方才知道数据什么时候发完。接收方通常不知道,强行关闭可能导致发送方往已关闭的 channel 发数据,直接 panic。
// 生产者:负责关闭
func producer(out chan<- int) {
defer close(out)
for i := 0; i < 10; i++ {
out <- i
}
}
// 消费者:只管收
func consumer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
Notechannel 不是文件,不需要每个都关。关闭的唯一目的是通知接收方「没数据了」。如果你只是用 channel 传几个值然后程序就结束了,不关也行。只有配合
range或需要接收方退出循环时才必须关。
会 panic 的情况
以下操作会让 channel 直接 panic:
// 1. 关闭已关闭的 channel
ch := make(chan int)
close(ch)
close(ch) // panic: close of closed channel
// 2. 往已关闭的 channel 发送
ch := make(chan int)
close(ch)
ch <- 1 // panic: send on closed channel
// 3. 关闭 nil channel
var ch chan int
close(ch) // panic: close of nil channel
多发送者的关闭难题
如果有很多个 goroutine 往同一个 channel 发数据,谁负责关?谁都不好关,因为关早了别人还在发。
解决办法:再加一个协调 channel,用一个中间人统一关闭。关键是让所有发送者都发完信号再关:
func main() {
ch := make(chan int)
done := make(chan struct{}, 2) // 缓冲 2,两个发送者各发一次
// 中间人:等两个发送者都完成后关闭 ch
go func() {
<-done
<-done
close(ch)
}()
// 发送者 1
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
done <- struct{}{}
}()
// 发送者 2
go func() {
for i := 5; i < 10; i++ {
ch <- i
}
done <- struct{}{}
}()
for v := range ch {
fmt.Println(v)
}
}
中间人收齐两个 done 信号才关 ch,这时两个发送者都已经发完了,不会出现往已关闭 channel 发数据的 panic。
实际项目里更推荐用 context 或 sync.WaitGroup 配合,逻辑更清晰。这些后面会讲。
小结
close(ch)关闭 channel,关闭后不能再发送- 接收时
v, ok := <-ch,ok为false表示已关闭且无数据 for v := range ch持续接收直到关闭- 原则:发送方负责关闭,接收方别关
- 不是每个 channel 都需要关,配合
range时才必须 - 重复关闭、发送到已关闭、关闭 nil 都会 panic
下一节学几个经典并发模式。