无缓冲通道能同步goroutine,因为发送操作ch

无缓冲通道为什么能同步 goroutine
因为发送操作 ch 在无缓冲通道上会**立即阻塞**,直到有另一个 goroutine 执行 <code> 接收;反过来,接收操作也会阻塞,直到有发送方就绪。这种“双方必须同时到场”的 rendezvous 机制,天然构成同步点。
它不是靠计数或状态标记,而是靠调度器强制暂停 goroutine,等配对操作出现才继续——所以不用额外加锁,也不依赖外部变量。
- 必须成对使用:每个
ch 都要有对应的 <code>,否则必死锁 - 通道类型无关紧要:用
chan struct{}比chan bool更语义清晰、零内存开销 - 不能复用:同一个无缓冲通道只能用于一次同步握手,重复发送会卡死
什么时候该用 chan struct{} 而不是 sync.WaitGroup
当你需要**带信号语义的等待**,比如“等某个任务完成并可能带回错误/结果”,或者要集成超时、取消逻辑时,chan struct{} 更直接。
sync.WaitGroup 只回答“是否全部 done”,而通道能回答“谁先 done”“done 时发生了什么”“是否 timeout”。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 要响应
ctx.Done()?用select+chan struct{},WaitGroup不支持 - 要等多个 goroutine 但只关心第一个完成?
select多个chan struct{}即可,WaitGroup必须全等 - 并发量极大(>10k)且纯计数?
WaitGroup内存和原子操作开销更低
close(ch) 和 ch 哪个更适合做完成信号
用 ch 。关闭通道是“终止写入”的语义,不是“发信号”的语义;误关已关闭的通道会 panic,而且接收方无法区分“关闭”和“零值接收”。
尤其当通道被多个 goroutine 共享(如 worker pool 的退出通知),close() 容易引发竞态或 panic;而发送一个空结构体是安全、单次、可预测的。
- 永远不要在不确定是否已关闭的通道上调用
close() - 如果真要用
close(),确保只有**唯一写入者**,且只 close 一次 ch 可以配合 <code>select实现非阻塞尝试发送,close()不行
常见死锁陷阱和怎么绕过
最典型的是:goroutine panic 了,没走到 ch 那行;或者主 goroutine 少写了一个 <code>;或者通道被声明为 <code>nil 后直接读写。
这些都会让程序卡在 `
- 所有发送路径都包一层
defer:比如defer func() { ch ,但注意 defer 在 panic 后仍执行 - 避免在循环里反复创建同名通道变量,容易误用旧 channel
- 启动 goroutine 前检查通道是否为
nil:用if ch == nil { panic("nil channel") }
真正难调试的不是语法错,而是逻辑漏——同步点漏一个,整个流程就悬在那里,连 pprof 都看不出 goroutine 在等什么。

















