
本文详解go中无缓冲channel如何在生产者与消费者goroutine间实现天然同步,解释为何`produce()`在发送完所有数据前会阻塞,以及`range`循环比无限`for{}`更安全的实践原因。
在Go的并发模型中,无缓冲通道(unbuffered channel)本质上是一个同步原语,而非单纯的数据队列。它要求每次发送(ch
回顾你的原始代码:
var msgs = make(chan int) // 无缓冲通道!
当produce()执行 msgs 立即挂起(block),直到consume()恰好执行到
✅ 发送与接收严格配对、逐个同步;
❌ produce()无法“批量投递”后继续执行后续逻辑(如close(msgs));
❌ 若消费者处理过慢或未启动,生产者将永久阻塞。
你的输出中缺失Consumer: 9及后续日志,正是因为:当produce()在第10次发送(即i == 9)时被阻塞,close(msgs)和done 死锁(deadlock)。
✅ 正确解法:用range安全消费 + 显式关闭
将consume()改为使用range遍历通道,它会自动在通道关闭后退出循环:
立即学习“go语言免费学习笔记(深入)”;
func consume() {
for msg := range msgs { // 遇到closed channel自动退出
time.Sleep(100 * time.Millisecond)
fmt.Println("Consumer:", msg)
}
fmt.Println("Consumer exited gracefully")
}同时确保produce()在循环结束后必须关闭通道(你原代码已正确做到),这是通知消费者“数据已发完”的唯一可靠方式。
⚠️ 关键注意事项
- 永远不要对已关闭的通道再次发送:close()只能调用一次,且应在所有发送完成后。
- 避免在消费者未启动时关闭通道:若close(msgs)早于go consume()执行,range会立即退出,导致数据丢失。
- 无缓冲通道 = 同步点:它强制生产者等待消费者就绪,天然防止数据积压,但要求双方生命周期协调。
- 调试技巧:如示例中添加fmt.Println("sending")/("sent"),可清晰观测goroutine的阻塞/唤醒时机。
通过理解无缓冲通道的同步本质,你不仅能修复死锁,更能掌握Go并发设计的核心哲学:通过通信共享内存,而非通过共享内存通信。










