Go并发场景下能否省略defer关键字?两种写法是否等价?
结论
两段Go代码执行逻辑并不完全等价,你测试时觉得两者表现一致,只是没有触发异常分支而已,生产场景下不建议省略defer关键字。
表现一致的前提
当协程内的逻辑完全按预期跑完整个for循环,中途既没有提前return/break退出,也没有触发任何panic时,两个写法的行为确实没有区别:都是完成n次向channel发送数据的操作后,调用一次wg.Done()扣减WaitGroup计数,这也是你日常测试觉得两者效果相同的原因。
两段实现的代码对比如下:
带defer的实现
go func() { defer wg.Done() for i := 0; i < n; i++ { ch <- i } }()
不带defer的实现
go func() { for i := 0; i < n; i++ { ch <- i } wg.Done() }()
核心行为差异
- panic场景表现完全不同:如果循环过程中触发panic——最常见的就是channel被其他协程提前关闭,往已关闭的channel写数据会直接触发panic。这种场景下不带defer的版本根本走不到末尾的
wg.Done()调用,WaitGroup的计数永远减不下去,外层的wg.Wait()会永久阻塞,直接导致程序死锁。而defer注册的wg.Done()不管协程是正常退出还是panic退出,都会保证执行,至少不会额外引入死锁问题。 - 代码迭代的容错能力差距极大:如果后续你给这个协程加提前退出逻辑,比如循环里判断到某个错误直接return、满足特定条件break跳出,只要没在所有退出路径上手动补
wg.Done(),不带defer的版本就会漏调用,直接出死锁bug。带defer的版本不管你走哪条路径退出协程,wg.Done()都会自动执行,不会漏。
能不能省略defer?
只有当你能100%保证这段协程逻辑永远不会触发panic、永远不会加任何提前退出分支、后续维护的人也不会改动这段逻辑的退出路径时,才可以把wg.Done()写在函数末尾省掉defer。但这种写法非常脆弱,属于给后续维护埋雷的行为。
而且Go 1.14之后defer的性能开销已经优化到极低,这种协程生命周期级别的defer调用,性能损耗完全可以忽略,根本没必要为了几乎感知不到的性能牺牲代码健壮性。
内容的提问来源于stack exchange,提问作者Stacy Lin
相关产品推荐
相关产品推荐

