Go 入门(21):channel 基础——无缓冲 vs 有缓冲,发送接收 close
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 21 篇,接 sync.WaitGroup。Go 的并发哲学是"不要通过共享内存来通信,而要通过通信来共享内存",channel 就是这个通信的载体。
本文要回答的问题
- 无缓冲 channel 和有缓冲 channel 有什么区别?
- 发送和接收分别在什么时候阻塞?
- close 之后还能读吗?还能写吗?
- 怎么用 range 遍历 channel?什么时候退出?
一、创建 channel
go
// 无缓冲 channel
ch := make(chan int)
// 有缓冲 channel,容量是 3
ch := make(chan int, 3)二、无缓冲 channel:同步通信
go
ch := make(chan int)
// goroutine A:发送
go func() {
ch <- 42 // 发送,阻塞直到有人接收
}()
// goroutine B:接收
value := <-ch // 接收,阻塞直到有人发送
fmt.Println(value) // 42无缓冲 channel 的发送和接收是同步的:
- 发送者在数据被接收之前一直阻塞
- 接收者在收到数据之前一直阻塞
所以无缓冲 channel 既用来传数据,也用来做同步——"发送完成"隐含"接收者已经拿到了"。
三、有缓冲 channel:异步通信
go
ch := make(chan int, 3)
ch <- 1 // 不阻塞,缓冲区还有空间
ch <- 2 // 不阻塞
ch <- 3 // 不阻塞
// ch <- 4 // 阻塞!缓冲区满了
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3有缓冲 channel 的发送只在缓冲区满时阻塞,接收只在缓冲区空时阻塞。
| 对比 | 无缓冲 | 有缓冲 |
|---|---|---|
| 容量 | 0 | 指定容量 |
| 发送阻塞 | 立即阻塞,直到被接收 | 缓冲区满才阻塞 |
| 接收阻塞 | 立即阻塞,直到有人发送 | 缓冲区空才阻塞 |
| 用途 | 同步、数据传递 | 生产消费、流量控制 |
四、close:关闭 channel
go
ch := make(chan int, 3)
ch <- 1
ch <- 2
close(ch) // 关闭 channel,不再发送关闭后的规则:
- 不能再发送:
ch <- 3会 panic - 可以继续接收:
<-ch能继续读到缓冲区里的数据 - 读空后返回零值:缓冲区读完后,
<-ch返回零值,不阻塞 - 可以用
v, ok := <-ch判断 channel 是否关闭,ok=false说明关闭了
go
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
v1, ok := <-ch // 1, true
v2, ok := <-ch // 2, true
v3, ok := <-ch // 0, false → channel 已关闭且没有数据了五、range 遍历 channel
go
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
fmt.Println(v) // 1 2 3
}
// channel 关闭且读空后,range 自动退出for range ch 会一直读,直到 channel 关闭且数据读空。如果不 close,range 永远不退出,会死锁。
六、单向 channel
函数参数可以限制 channel 方向:
go
// 只发不收
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
// 只收不发
func consumer(ch <-chan int) {
for v := range ch {
fmt.Println(v)
}
}
func main() {
ch := make(chan int, 3)
go producer(ch)
consumer(ch)
}chan<- 表示只能发送,<-chan 表示只能接收。编译期检查,写反了报错。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 向已关闭的 channel 发送 | panic | 永远不在接收方 close |
| 不 close 就 range | 永远阻塞,死锁 | 发送方 close 后,range 自动退出 |
| 接收方等待永远不会发送的数据 | 死锁 | 确保发送/接收数量匹配 |
| 无缓冲 channel 的生产者和消费者不匹配 | 死锁 | 用有缓冲或调换 goroutine 执行顺序 |
相关与延伸
下一篇:select 多路复用——同时监听多个 channel;channel 的底层实现是环形缓冲区 + 等待队列,进阶层 goroutine 与 channel 深度讲。
一句话总结
Go channel:make(chan T) 无缓冲,同步——发送阻塞等接收,接收阻塞等发送;make(chan T, n) 有缓冲,异步——缓冲区满才阻塞发送,空才阻塞接收;close(ch) 关闭后不能发送,能继续接收,读空返回零值;range ch 遍历直到 close;函数参数用 chan<- 和 <-chan 限制方向,编译期检查。