自学内容网 自学内容网

Go基础编程 - 15 - 延迟调用(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)
}

以上代码执行顺序及输出结果如下图:

  1. 函数内 defer,在函数执行结束前执行。
  2. main 函数内,关键字 defer 在 main 函数内按先进后出顺序在 main 函数执行结束前执行,其它代码按顺序执行。
  3. defer1() 函数内关键字 defer 在 defer1 函数内按先进后出顺序执行。
  4. 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
    }
    

原文地址:https://blog.csdn.net/iuhart/article/details/142551446

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!