不能直接 close(channel) 后继续 send,否则触发 panic: send on closed channel;生产者需在完成任务后仅 close 一次,消费者须用 for v, ok := range ch 或 for { select { case v, ok :=

Go 用 channel + goroutine 实现生产者消费者模型,核心是避免手动加锁、不依赖第三方库,但必须注意缓冲区容量和关闭时机,否则容易死锁或 panic。
为什么不能直接 close(channel) 后继续 send
向已关闭的 chan 发送数据会触发 panic:panic: send on closed channel。生产者退出前若未协调好关闭顺序,消费者还在读,就极易出错。
- 生产者完成任务后应调用
close(ch),仅此一次 - 消费者必须用
for v, ok := 循环读取,靠ok判断 channel 是否关闭 - 不要在多个生产者中随意 close —— 只有最后一个完成的生产者才该关
带缓冲的 channel 和无缓冲 channel 性能差异明显
无缓冲 chan int 要求生产者和消费者严格同步(send 和 receive 必须同时就绪),适合强顺序控制;带缓冲如 make(chan int, 100) 允许短暂解耦,吞吐更高,但缓冲区过大会掩盖背压问题。
- 缓冲大小建议设为预期峰值并发量 × 单次批处理量,例如日志采集场景常用
make(chan *LogEntry, 1024) - 若消费者处理慢、缓冲满,生产者 goroutine 会阻塞在
ch ,这是天然背压机制,别急着加超时或丢弃逻辑 - 用
len(ch)查当前队列长度(非缓冲区容量),可用于监控积压情况
如何安全支持多个生产者 + 多个消费者
多生产者共用一个 channel 没问题,但关闭需额外同步;多个消费者可并行读同一 channel,无需额外锁。
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.WaitGroup管理所有生产者完成信号,由主 goroutine 统一 close - 消费者数量不建议动态伸缩 —— 启动时固定启动 N 个
go consumer(ch)即可 - 若需优雅退出(如收到
os.Interrupt),应通过额外的done chan struct{}通知所有 goroutine,而非直接 close 工作 channel
package mainimport ( "fmt" "sync" "time" )
func main() { ch := make(chan int, 5) var wg sync.WaitGroup
// 启动 2 个生产者 for i := 0; i < 2; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 3; j++ { ch <- id*10 + j time.Sleep(100 * time.Millisecond) } }(i) } // 启动 3 个消费者 for i := 0; i < 3; i++ { go func(id int) { for v := range ch { fmt.Printf("consumer %d got %d\n", id, v) time.Sleep(200 * time.Millisecond) } }(i) } // 等待所有生产者结束,再关闭 channel go func() { wg.Wait() close(ch) }() // 主 goroutine 不退出,等消费者自然结束 time.Sleep(3 * time.Second)}
真正难的不是写通这个模型,而是判断什么时候该用带缓冲 channel、什么时候该加超时、以及如何把「关闭 channel」这件事从业务逻辑里剥离开 —— 这些细节没处理好,上线后就是偶发 panic 或 goroutine 泄漏。










