
在 go 中,若需向多个 goroutine 广播“准备就绪”这一单次信号,直接向无缓冲通道发送一个值会导致后续接收方阻塞;正确做法是关闭通道——所有接收方均可立即收到零值并继续执行,从而安全实现一次触发、多处响应。
在 go 中,若需向多个 goroutine 广播“准备就绪”这一单次信号,直接向无缓冲通道发送一个值会导致后续接收方阻塞;正确做法是关闭通道——所有接收方均可立即收到零值并继续执行,从而安全实现一次触发、多处响应。
在 Go 并发编程中,常需用通道(channel)作为同步原语来协调多个 goroutine 的执行时机。一个典型场景是:主流程需等待某项初始化任务完成(如配置加载、连接建立),之后多个后续操作(如启动服务、运行任务)都应在此前提下开始。此时,若使用普通通道发送单个 true 值,会因通道未缓冲且仅能消费一次,导致第二个 操作永久阻塞,最终触发 <code>fatal error: all goroutines are asleep - deadlock!。
根本原因在于:
- 无缓冲通道的发送与接收必须同步配对;
接收操作在通道为空时会阻塞;- 即使第一个
waiting()成功接收了true,通道仍为空,第二个waiting()将无限等待,而done()已退出,无人再发送。
✅ 推荐解法:关闭通道(close(ch))
Go 规范明确规定:从已关闭的通道接收值,会立即返回该通道元素类型的零值(false for bool),且 ok 返回值为 false。更重要的是,关闭行为本身即是一种广播信号——所有正在或即将执行 的 goroutine 都会立即解除阻塞。这正是一次性就绪通知的理想模型。
以下是修正后的完整示例:
package main import "fmt" func done(ok chan<p><strong>输出结果</strong>:</p><pre class="brush:php;toolbar:false;">Ready and proceeding... Ready and proceeding... All Ok!
⚠️ 注意事项:
-
不可重复关闭通道:对已关闭通道再次调用
close()会引发 panic,因此确保done()最多执行一次(通常由单一 goroutine 负责); -
无需检查接收结果:本例中我们只关心“是否就绪”,不依赖接收值内容,故
即可;若需区分“显式发送”与“关闭信号”,可改用 <code>val, ok := 判断; -
通道类型选择:
chan bool仅作信号用途,内存开销极小;若需传递数据,可考虑chan struct{}(零内存占用)或带数据的通道配合关闭机制; -
适用范围:此模式适用于“事件完成通知”,不适用于需要多次通知的场景(此时应考虑
sync.Once、sync.WaitGroup或带缓冲的通道)。
总结:当需要“一次触发、多方响应”的同步语义时,关闭通道是最简洁、高效且符合 Go 惯用法的解决方案。它规避了手动管理信号计数的复杂性,利用 Go 运行时对关闭通道的原子广播特性,天然支持并发安全的就绪通知。










