首页 / Go 语言入门教程 / channel 关闭与遍历

Go 语言入门教程

channel 关闭与遍历

本教程共 80 篇 · 第 59 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程channelcloserange遍历并发

59. channel 关闭与遍历

本节目标:学会用 close 关闭 channel,用 range 遍历 channel,搞懂关闭原则和常见错误。

close 函数

close(ch) 关闭一个 channel。关闭后的 channel 有这些特性:

  • 不能再发送数据,发送会 panic
  • 可以继续接收,先把缓冲区剩余数据收完
  • 缓冲区空了之后,接收返回零值,第二个返回值 okfalse
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)

okfalse 就是告诉接收方:「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

如果发送方不 closerange 会一直等下一个数据。等所有 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)
	}
}
Note

channel 不是文件,不需要每个都关。关闭的唯一目的是通知接收方「没数据了」。如果你只是用 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。

实际项目里更推荐用 contextsync.WaitGroup 配合,逻辑更清晰。这些后面会讲。

小结

  • close(ch) 关闭 channel,关闭后不能再发送
  • 接收时 v, ok := <-chokfalse 表示已关闭且无数据
  • for v := range ch 持续接收直到关闭
  • 原则:发送方负责关闭,接收方别关
  • 不是每个 channel 都需要关,配合 range 时才必须
  • 重复关闭、发送到已关闭、关闭 nil 都会 panic

下一节学几个经典并发模式。