一、背景
早期管理资源(如数据库连接、锁、文件句柄、网络连接)和状态清理异常麻烦。必须在每个可能的返回点(return、err、panic)手动重复清理代码,极易遗漏且打断主要逻辑思路!
像 Java 语言虽然用了 Try-Catch,但缺点是逻辑不清晰、臃肿、不容易判断错误出在什么地方。
作为新生代的 Go,及众多语言之精华,推出了 defer 处理机制。尤其是在 Go 1.14 版本时,性能开销接近零,更无后顾之忧。
二、定义
1. 语法
defer functionCall(..arguments..)
defer 后面直接跟一个函数调用(可以是命名函数、匿名函数、方法等)。
2. 定义
当函数执行到 defer 语句时(注册时),它会立即求值此时该函数调用的参数,并将此次函数调用(包括已求值的参数)放到一个延迟调用表中。这个调用函数与 goroutine 关联,采用 LIFO(后进先出)的方式调用。切记这个延迟调用表不会立即执行,而是会等到函数真正结束之前——函数 (return 或 panic) 之后——再调用。
3. 特性
- 延迟执行:运行到 defer 时,只是将求值后的参数与调用的函数一并打包到延迟调用表中,需等到函数体结束之后在执行。
- LIFO 方式执行:后进先出的方式执行。
- 参数求值时机:defer 语句中的函数参数的值,是在执行到 defer 语句时(即注册时)就确定并保存下来的,而不是在延迟函数实际执行时才求值。
- 作用域:自各函数体。
三、应用
1. 临近释放(逻辑清晰)
mu.Lock()
defer mu.Unlock() // 好习惯!确保解锁
// ... 操作共享数据 ...
2. panic 捕获(防止程序崩溃)
特殊情况,根据源码分析——协程中出现 panic,若不能再该协程中捕获,则会导致整个程序崩溃。
func test() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic(1)
}
3. 循环函数释放(利用完资源后,及时释放资源)
// 正确做法:将文件处理封装到函数,defer 在每次循环的匿名函数结束时执行
func outerFunc() {
for _, filename := filenames {
{
f, err := os.Open(filename)
err != {
log.Println(err)
}
f.Close()
}()
}
}


