Go语言中的defer语句用于在函数返回前执行指定的操作,这个特性在资源释放、异常处理等场景中非常常用。很多开发者在使用goroutine时,发现内部的defer没有执行,就认为defer在goroutine内部不返回时不会执行,这个认知其实是不准确的。

defer的基本执行规则
defer的核心规则是:它会在所在的函数即将返回时执行,无论函数是正常返回还是因为panic异常退出。这个规则在普通函数和goroutine中是一致的,只是goroutine的退出场景更多样。
我们先看一个普通函数中的defer示例:
package main
import "fmt"
func testDefer() {
defer fmt.Println("defer执行了")
fmt.Println("函数正常执行")
return
}
func main() {
testDefer()
}
运行上面的代码,输出顺序是先打印函数正常执行,再打印defer执行了,符合defer的预期行为。
goroutine中defer的执行场景
场景1:goroutine正常执行完毕
当goroutine中的函数正常执行到结束,defer会正常执行:
package main
import (
"fmt"
"time"
)
func goroutineWithDefer() {
defer fmt.Println("goroutine中的defer执行了")
fmt.Println("goroutine正常执行")
}
func main() {
go goroutineWithDefer()
// 等待goroutine执行完成,避免主程序提前退出
time.Sleep(time.Second)
}
运行后可以看到,goroutine中的defer正常执行,输出goroutine正常执行和goroutine中的defer执行了。
场景2:goroutine中发生panic
如果goroutine中发生panic且没有recover,defer依然会执行,之后panic会导致程序崩溃:
package main
import (
"fmt"
"time"
)
func goroutineWithPanic() {
defer fmt.Println("panic前的defer执行了")
fmt.Println("goroutine即将panic")
panic("发生异常")
}
func main() {
go goroutineWithPanic()
time.Sleep(time.Second)
}
运行后可以看到defer正常执行,之后程序抛出panic错误。
场景3:主程序提前退出导致goroutine未执行完
这是最常见的defer看似不执行的情况:主程序退出后,所有未执行完的goroutine会被强制终止,此时defer自然没有机会执行。
package main
import "fmt"
func goroutineWithoutWait() {
defer fmt.Println("这个defer不会执行")
fmt.Println("goroutine开始执行")
// 模拟耗时操作,还没执行完主程序就退出了
for i := 0; i < 1000000000; i++ {
}
}
func main() {
go goroutineWithoutWait()
// 主程序没有等待,直接退出
}
运行上面的代码,主程序启动goroutine后立刻退出,goroutine还没执行到defer的部分就被终止了,所以看不到defer的输出,这并不是defer不执行,而是goroutine根本没有走完执行流程。
如何保证goroutine中的defer正常执行
要避免goroutine中的defer不执行的问题,核心是保证goroutine有足够的时间执行完整个流程,常用的方式有:
- 使用
time.Sleep等待,适合简单测试场景 - 使用
sync.WaitGroup等待所有goroutine执行完成,适合生产环境 - 使用channel通知主程序goroutine执行完毕
下面是使用sync.WaitGroup的示例:
package main
import (
"fmt"
"sync"
)
func goroutineWithWaitGroup(wg *sync.WaitGroup) {
defer wg.Done()
defer fmt.Println("goroutine中的defer执行了")
fmt.Println("goroutine正常执行")
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go goroutineWithWaitGroup(&wg)
wg.Wait()
}
这种方式可以确保主程序等待goroutine执行完毕,defer就能正常触发。
常见误区总结
很多开发者认为defer在goroutine内部不返回时不会执行,本质是把goroutine被强制终止的场景当成了defer不执行。实际上只要goroutine正常走完执行流程,无论是否显式写return,defer都会执行。如果goroutine中发生panic且有recover,defer也会执行,之后程序恢复正常。只有goroutine被外部强制终止(比如主程序退出、进程被杀死)时,defer才会没有执行的机会。