延迟调用 defer
- 1. 特性
- 2. 常用用途
- 3. defer 执行顺序:`同函数内`先进后出
- 4. defer 闭包
- 5. defer 陷阱
上一篇:泛型
1. 特性
1. 关键字 defer 用于注册延迟调用。
2. defer 调用直到 return 前才被执行。
3. 同函数内多个 defer 语句,按先进后出的方式执行。
4. defer 语句中的变量,在 defer 声明时就决定了。
2. 常用用途
滥用 defer 可能会造成性能问题,尤其是在一个“大循环”里。
1. 关闭文件句柄。
2. 锁资源释放。
3. 数据库连接释放。
3. defer 执行顺序:同函数内
先进后出
package main
import "fmt"
func defer1(i int) {
defer fmt.Printf("defer1 %d - 1\n", i)
defer fmt.Printf("defer1 %d - 2\n", i)
}
func main() {
// 函数内 defer,在函数执行结束前执行。
// 因此嵌套函数 defer1() 内的 defer 在自身函数内按先进后出的顺序执行。
// 且 defer1() 内的 defer 在 main 函数内的执行顺序仅取决于 defer1() 函数的执行顺序。
defer defer1(100)
defer1(200)
defer fmt.Println("main 1")
defer1(300)
defer defer1(400)
}
以上代码执行顺序及输出结果如下图:
- 函数内 defer,在函数执行结束前执行。
- main 函数内,关键字 defer 在 main 函数内按先进后出顺序在 main 函数执行结束前执行,其它代码按顺序执行。
- defer1() 函数内关键字 defer 在 defer1 函数内按先进后出顺序执行。
- defer1() 函数内的输出在自身函数执行结束前全部输出。
多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
func main() {
defer println(1)
defer println(2)
i := 0
defer func() {
println(100 / i) // 此处抛出 panic,但下面 defer 依然执行
}()
defer println(3)
}
输出结果
3
2
1
panic: runtime error: integer divide by zero
4. defer 闭包
defer
语句适用于单个函数或语句的延迟执行。当需要执行更复杂的逻辑或者多个操作时,可以使用 defer func ()
闭包。
-
每次执行 defer 语句时,
函数值
和调用的参数
都会进行求值并保存
,但不执行实际的函数。 也就是复制了一份。func main() { i := 1 defer fmt.Println("one - i =", i) // i 被复制保存 // defer 使用了闭包, 此时仅定义了闭包函数,并未执行。 defer func() { fmt.Println("two - i =", i) // 闭包引用 i }() // 变量 i 作为函数参数传入时,会进行求值并保存。 defer func(n int) { fmt.Println("three - i =", n) }(i) // i 被复制保存 i = 100 defer func(i int) { fmt.Println("four - i =", i) }(i) // i 被复制保存 fmt.Println("i =", i) }
输出结果:
i = 100 four - i = 100 three - i = 1 two - i = 100 one - i = 1
-
defer 中可以使用指针或闭包“延迟”读取。
func main() { x, y, z := 10, 10, 10 // 延迟读取 y,z 的值 defer func(i int, j *int) { fmt.Printf("defer: x = %d, y = %d, z = %d \n", i, *j, z) // y 指针传递,z 闭包引用 }(x, &y) // x 被复制 x += 100 y += 100 z += 100 fmt.Printf("main: x = %d, y = %d, z = %d\n", x, y, z) }
输出结果:
main: x = 110, y = 110, z = 110 defer: x = 10, y = 110, z = 110
5. defer 陷阱
-
defer 与闭包、return
package main import ( "errors" "fmt" "os" ) // 如果 defer 后不是一个闭包,最后执行的时候我们得到的并不是最新的值,而是声明 defer 时保存的值。 // 有具名返回值函数中,实际值为return前最终计算结果。 func closure(i, j int) (n int, err error) { defer fmt.Printf("1 n = %d, defer: %v\n", err) // n, err 复制保存当前值 defer func(err error) { fmt.Printf("2 n = %d, defer: %v\n", err) }(err) // err 保存当前值传参;n 为全局变量 defer func() { fmt.Printf("3 n = %d, defer: %v\n", err) }() // 闭包引用 if j == 0 { err = errors.New("divided by zero") return } return i / j, nil } // 使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。 // 1. 示例中打开test.txt、test2.txt都赋值给变量 f。 // 2. defer 声明使用闭包函数;在执行defer声明的代码时,f 已经被重新赋值为 test2.txt,导致 test.txt 关闭失败。 // 报错:close failed:test.txt close test2.txt: file already closed // 3. 优化建议:避免使用相同变量;或 defer 声明使用值参传参 func closureCover() { f, err := os.Open("test.txt") if err != nil { fmt.Println("open failed: test.txt") } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Println("close failed:test.txt", err) } }() // 优化为值传递 /* defer func(f *os.File) { ... }(f) */ } f, err = os.Open("test2.txt") if err != nil { fmt.Println("open failed: test2.txt") } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Println("close failed:test2.txt", err) } }() // 优化同上 } } func main() { closure(2, 0) // 输出: // 3 n = 0, defer: divided by zero // 2 n = 0, defer: <nil> // 1 n = 0, defer: <nil> // 有具名返回值函数中,实际值为return前最终计算结果。输出: // 3 n = 5, defer: <nil> // 2 n = 5, defer: <nil> // 1 n = 0, defer: <nil> }
-
defer nil 函数
func main() { defer func() { if err := recover(); err != nil { fmt.Println("recover:", err) } }() var run func() = nil defer run() fmt.Println("running") // 输出: // running // recover: runtime error: invalid memory address or nil pointer dereference }
-
在错误位置使用 defer
package main import ( "fmt" "net/http" ) func main() { res, err := http.Get("http://www.xxxxxxxxxxx") defer res.Body.Close() if err == nil { return } fmt.Println("running") // 输出 // panic: runtime error: invalid memory address or nil pointer dereference }
当 http.Get 失败时 res 为 nil,我们在 defer 调用 res.Body 时并未判断是否执行成功,会抛出异常。优化如下:
func main() { res, err := http.Get("http://www.xxxxxxxxxxx") if err == nil { // 判断 http.Get 执行成功时,才需要关闭响应连接 defer res.Body.Close() } fmt.Println("running") // 输出 // running }