讲解
channel 是 goroutine 之间传递数据的管道,体现了 Go 的座右铭:「不要通过共享内存来通信,要通过通信来共享内存」。make(chan int) 创建无缓冲 channel:发送方 ch <- v 会阻塞,直到有接收方 <-ch 取走,一手交钱一手交货,天然带同步效果;make(chan int, 3) 创建带缓冲的 channel,缓冲区没满时发送不阻塞。
发送方完成后用 close(ch) 关闭 channel,接收方用 for v := range ch 一直收到 channel 关闭为止——「发送方关闭、接收方用 range」是标准分工。接收方也可以用 v, ok := <-ch 的 comma ok 形式判断是否已关闭。对已关闭的 channel 再发送会 panic,从关闭的 channel 接收则立即得到零值加 false。
select 是 channel 的多路复用器:同时等待多个 channel 操作,谁先就绪执行谁,都有就绪就随机挑一个。经典用法是配 time.After 实现超时控制——select { case v := <-ch: ...; case <-time.After(d): 超时 }。掌握 goroutine + channel + select 这三件套,就掌握了 Go 并发编程的骨架。
示例
无缓冲 channel:goroutine 之间的一次握手:
package main
import "fmt"
func square(n int, ch chan int) {
ch <- n * n // 发送,阻塞直到有人接收
}
func main() {
ch := make(chan int)
go square(7, ch)
result := <-ch // 阻塞直到收到值
fmt.Println("7 的平方:", result)
}
带缓冲 channel、close 与 range 接收:
package main
import "fmt"
func main() {
ch := make(chan string, 3)
ch <- "苹果"
ch <- "香蕉"
ch <- "橙子"
close(ch) // 发送方关闭,range 才能正常结束
for fruit := range ch {
fmt.Println("收到:", fruit)
}
}
select 实现超时控制:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(50 * time.Millisecond)
ch <- "慢任务的结果"
}()
select {
case msg := <-ch:
fmt.Println("收到:", msg)
case <-time.After(200 * time.Millisecond):
fmt.Println("超时了")
}
}
常见坑
- 无缓冲 channel 自我死锁:同一个 goroutine 里先 ch <- v 再 <-ch 必然卡死——发送时还没有接收者。无缓冲 channel 的发送和接收必须分布在不同的 goroutine。
- 向已关闭的 channel 发送:直接 panic。纪律:只有发送方(且唯一)负责 close;多个发送方时用 WaitGroup 等全部发完再由专人关闭。
- 关闭不是必须的:channel 不关也能正常收发,GC 会回收。close 的唯一作用是告诉接收方「不会再有新值了」,用于 range 退出或广播完成信号。
- select 里忘记 default 的语义:加 default 变成非阻塞轮询(没有就绪就走 default),不加则阻塞等待。想要哪种语义要想清楚,轮询写法在循环里还会空转烧 CPU。
- goroutine 泄漏:goroutine 卡在永远不会有人接收的发送操作上,就永远退不出来,内存和协程数持续上涨。确保每个发送最终都有接收方,或用带超时的 select 兜底。
小结
channel 在 goroutine 间传值,无缓冲同步、有缓冲异步,发送方 close、接收方 range,select 做多路等待与超时。下一节回到日常业务:JSON 编解码。