本节摘要:channel 是类型化的并发安全队列,也是 goroutine 间传递数据与信号的桥梁。本节覆盖声明与初始化、发送接收的阻塞语义、无缓冲与有缓冲的同步/异步分野、关闭的正确姿势与 range 遍历、单向通道的 API 约束,以及信号通知、扇入、退出信号、并发限流等高级模式。
阅读完本节,你应当能够:
channel 是一个带类型的先进先出队列,收发两端在任何数量的 goroutine 间都是并发安全的(channel 自身保证,不需要加锁)。三种声明:
var c1 chan int // nil通道,未初始化 c2 := make(chan int) // 无缓冲 c3 := make(chan int, 10) // 缓冲容量10
操作就三个:箭头向里发送 c2 <- 1,箭头向外接收 <-c2,内建 close 关闭。nil 通道的语义是"永久阻塞"——收发都卡死,这个特性后面在 select 里有妙用。
无缓冲通道是同步点。发送方阻塞直到接收方就位,接收方阻塞直到发送方就位——两边"握手"成功才同时继续。它不存数据,它是约会:
done := make(chan bool) go func() { work() done <- true // 等 main来收,才返回 }() <-done // main阻塞等工作完成
有缓冲通道是异步队列。缓冲没满,发送立即返回;满了才阻塞。接收在缓冲非空时立即成功,空了阻塞。容量是"允许超前多少步"的额度:
tasks := make(chan int, 3) tasks <- 1 // 立即返回(占用1格) tasks <- 2 tasks <- 3 // tasks <- 4 // 此处会阻塞,直到有人取走一格
| 操作 | nil 通道 | 无缓冲 | 有缓冲(未满/非空) | 有缓冲(满/空) | 已关闭 |
|---|---|---|---|---|---|
| 发送 | 永久阻塞 | 等接收方 | 立即 | 阻塞 | panic |
| 接收 | 永久阻塞 | 等发送方 | 立即 | 阻塞 | 立即返回缓冲剩余,之后返回零值 |
这张表值得背下来——Go 并发面试的一半考它。

close 表示"没有更多数据了"。接收方两种感知方式:双返回值 v, ok := <-c(ok 为假即已关闭且排空),或者更惯用的 range——通道关闭后循环自动结束:
jobs := make(chan int, 5) go func() { for i := 1; i <= 5; i++ { jobs <- i } close(jobs) // 发送方负责关闭 }() for j := range jobs { // 关闭即退出循环 fmt.Println("处理", j) }
关闭不是"必须的善后":垃圾回收会回收不再使用的通道,close 的唯一目的是通知接收方数据流结束。不需要通知就不关;拿不准就不关——泄漏一个没人等的 channel 比误关导致 panic 温柔得多。
func produce(out chan<- int) { ... } // 只能发 func consume(in <-chan int) { ... } // 只能收
双向通道可以赋给单向,反过来不行。价值在 API 自解释:参数类型直接告诉调用方"我只发不收",编译器替你站岗。
模式一:完成信号(前文 done 通道),无缓冲、发一个布尔或直接 close(done)——关闭广播给所有接收方,一发多收用关闭最省。
模式二:结果收集。开 N 个 worker 各算各的,结果统一送进一个通道,主流程收满 N 个。通道自带锁,不需要任何额外同步。
模式三:扇入合并。多个来源通道的数据汇入一条流,用 select 轮流搬(见下节)或每个来源一个搬运 goroutine。
模式四:并发限流。缓冲通道当信号量:
sem := make(chan struct{}, 3) // 最多3并发 for _, t := range tasks { sem <- struct{}{} // 满3则阻塞,天然限流 go func(id int) { defer func() { <-sem }() // 干完释放一格 work(id) }(t) }
空结构体零字节,是"只占坑不装货"的标准选择。
⚠️ 常见坑:主流程用 time 加睡眠"等" goroutine 干完——竞态赌博不是同步。要么 WaitGroup,要么收结果通道,让完成可观测。
💡 关键直觉:channel 是传送带不是储物柜——设计时先想"谁给谁、什么时候给、什么时候停",容量只是节奏调节旋钮。
问答补遗。channel 容量选多大? 默认零(显式同步最易推理);确有速率差再给缓冲,容量以"下游消费一批的量"为参考,别拍脑袋给一万。怎么知道 channel 该关了? 只有"多接收方等结束通知"时才需要关;一对一传值甚至可以从不关闭,交给 GC。发送方多个谁关? 谁都不许擅关——多发送方场景由"最后一个发完的人"或统一的协调者关闭,或干脆改用 WaitGroup 加集中发送。channel 会内存泄漏吗? 没人收也没人发的满缓冲通道会连数据一起被 GC 收走;真正泄漏的是"卡在收发上的 goroutine"——对象是通道,病因在退出设计。
下一节给通道装上调度台:select。