本文详解 go 语言中向未缓冲 channel 发送数据时发生阻塞的根本原因,并提供两种可靠解决方式:启动 goroutine 实现并发收发,或使用带缓冲 channel 避免同步依赖。
本文详解 go 语言中向未缓冲 channel 发送数据时发生阻塞的根本原因,并提供两种可靠解决方式:启动 goroutine 实现并发收发,或使用带缓冲 channel 避免同步依赖。
在 Go 中,channel 是协程间通信的核心机制,但其行为高度依赖于缓冲类型与并发模型。原代码中 ch := make(chan bool) 创建的是一个未缓冲(unbuffered)channel,它要求发送(ch <- true)和接收(<-ch)操作必须同时就绪——即发送方和接收方需在不同 goroutine 中同步执行。而原程序仅有一个主线程(main goroutine),self.quack(ch) 在 MoneyDive() 中被同步调用,导致 ch <- true 永远等待接收方,程序在此处永久阻塞。
✅ 正确方案一:启用 goroutine 并发执行
将 quack 调用移至新 goroutine,使发送与接收可并行进行:
func (self *DagobertDuck) MoneyDive() {
ch := make(chan bool)
go self.quack(ch) // 启动新 goroutine 执行发送
b := <-ch // 主 goroutine 等待接收
fmt.Println(b) // 直接打印,无需 if 判断
}此时执行流程为:
- 主 goroutine 创建 channel → 启动 mockQuack goroutine → 立即进入接收 <-ch
- mockQuack goroutine 执行 ch <- true → 主 goroutine 接收成功 → 输出 true
✅ 输出顺序:mockQuack start → mockQuack done → true
立即学习“go语言免费学习笔记(深入)”;
✅ 正确方案二:使用带缓冲 channel
若逻辑上无需严格并发,也可通过缓冲避免同步等待:
func (self *DagobertDuck) MoneyDive() {
ch := make(chan bool, 1) // 缓冲容量为 1,允许一次无接收的发送
self.quack(ch) // 同步调用,立即返回(因有缓冲空间)
fmt.Println(<-ch) // 接收已存入的值
}注意:缓冲大小必须 ≥ 1 才能容纳发送值;若后续再次 ch <- ... 且未及时接收,仍会阻塞。
⚠️ 关键注意事项
- 永远不要对 nil channel 发送或接收:会导致永久阻塞;
- 关闭已关闭的 channel 或向关闭 channel 发送:触发 panic;
- 过度依赖 channel 处理简单逻辑可能增加复杂度:如本例中 mockQuack() 完全可用 func() bool 替代,更简洁高效;
- fmt.Println(<-ch) 可省略中间变量,但需确保 channel 必然有值发送,否则仍会阻塞。
掌握 channel 的阻塞特性与 goroutine 协作模式,是写出健壮、可维护 Go 并发代码的基础。


















