• (十)defer关键字


    golang中的defer关键字用来声明一个延迟函数,该函数会放在一个列表中,在defer语句的外层函数返回之前系统会执行该延迟函数。defer特点有:

    • 函数返回之前执行
    • 可以放在函数中任意位置
    • 可以同时设置多个defer函数,多个defer函数执行遵循FILO顺序
    • defer函数的传入参数在定义时就已经明确
    • 可以修改函数中的命名返回值 用于文件资源,锁资源、数据库连接等释放和关闭
    • 和recover一起处理panic

    1. defer会在函数返回之前执行

    当程序执行一个函数时候,会将函数的上下文(输入参数,返回值,输出参数等信息)作为栈帧放在程序内存的栈中,当函数执行完成之后,设置返回值并返回,此时栈帧退出栈,函数才真正完成执行。

    defer语句函数会在函数返回之前执行,下面程序将会依次输出B A:

    func main() {
        defer fmt.Println("A")
        fmt.Println("B")
    }
    
    • 1
    • 2
    • 3
    • 4

    2. defer可以放在函数中任意位置

    func main() {
        fmt.Println("A")
        defer fmt.Println("B")
        fmt.Println("C")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面程序将会依次输出A C B

    注意:

    1. defer语句一定要在函数return语句之前,这样才能生效。下面程序将会只输出A
    func main() {
        fmt.Println("A")
        return 
        fmt.Println("B")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 在调用os.Exit时候,defer不会执行。下面程序只会输出B
    func main() {
        defer fmt.Println("A")
        fmt.Println("B")
        os.Exit(0)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. 可以同时设置多个defer函数

    可以设置多个defer函数,多个defer函数执行遵循FILO顺序,下面程序将依次输出B D C A

    func main() {
        defer fmt.Println("A")
        fmt.Println("B")
        defer fmt.Println("C")
        fmt.Println("D")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们来看下多个defer嵌套情况:

    func main() {
        fmt.Println("A")
        defer func() {
           fmt.Println("B")
           defer fmt.Println("C")
           fmt.Println("D")
        }()
        
        defer fmt.Println("E")
        fmt.Println("F")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面程序将依次输出: A F E B D C

    defer语句内部实现形式是一个结构体:

    # 位于/usr/lib/go/src/runtime/runtime2.go#784
    type _defer struct {
        ...
    	sp      uintptr // 函数栈指针,sp是stack pointor单词首字母缩写
    	pc      uintptr //程序计数器, pc是program counter单词首字母缩写
    	fn      *funcval // 函数地址,执行defer函数
    	_panic  *_panic // 指向最近一次panic
    	link    *_defer // 指向下一个_defer结构
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    defer内部实现是一个链表,链表元素类型是_defer结构体,其中的link字段指向下一个_defer地址,当定义一个defer语句时候,系统内部会将defer函数转换成_defer结构体,并放在链表头部,最后执行时候,系统会从链表头部开始依次执行,这也就是多个defer的执行顺序是First In Last out的原因。

    4. defer函数的传入参数在定义时就已经明确

    defer函数的传入参数在定义时就已经明确,不论传入的参数是变量、表达式、函数语句,都会先计算出计算出实参结果,再随defer语句入栈

    func main() {
      i := 1
      defer fmt.Println(i)
      i++
      return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面程序输出1,而不是2

    注意:

    当defer类似闭包使用时候,访问的总是循环中最后一个值

    func main() {
        for i:=0; i<5; i++ {
            defer func() {
               fmt.Println(i) 
            }()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面程序连续输出5个5

    解决办法可将值传入闭包函数中,此时defer函数入栈时候,不光入栈地址,还会记录传入参数,等到执行的时候也就打印输出入栈时候的值

    func main() {
        for i:=0; i<5; i++ {
            defer func(i int) {
               fmt.Println(i) 
            }(i)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此时依次输出4 3 2 1 0

    5. 多用于文件资源关闭,数据库等连接关闭

    处理资源释放回收

    通过defer我们可以简洁优雅处理资源回收问题,避免复杂的代码逻辑情况下,遗漏忽视相关的资源回收问题。

    我们看下下面的代码,目的是复制文件内容到一个新文件

    func CopyFile(dstName, srcName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
            return
        }
    
        dst, err := os.Create(dstName)
        if err != nil {
            return
        }
    
        written, err = io.Copy(dst, src)
        dst.Close()
        src.Close()
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面代码是存在bug的。当文件创建失败的时候,直接返回了,却没有对打开的文件资源关闭回收。

    通过defer我们可以保证始终能够正确的关闭资源, 并且处理逻辑简单优雅。

    func CopyFile(dstName, srcName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
            return
        }
        defer src.Close()
    
        dst, err := os.Create(dstName)
        if err != nil {
            return
        }
        defer dst.Close()
    
        return io.Copy(dst, src)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    和recover一起处理panic

    recover用户捕获panic异常,panic用于抛出异常。recover需要放在defer语句中,否则无法捕获到一次

    下面这个例子将会捕获到panic, 并且输出panic信息

    func main() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println(r)
            }
        }()
        panic("it is panic")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    多个恐慌同时发生时候,只会捕获第一个恐慌

    func main() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println(r)
            }
        }()
        panic("it is panic")
        panic("it is another panic")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    PHP反序列化漏洞
    图像识别技术在工业自动化领域的应用与实现
    【Linux】ps -ef 和ps aux 有什么不同呢?
    Go 语言 new 和 make 关键字的区别
    leetcode-1964:找出到每个位置为止最长的有效障碍赛跑路线
    每日一考-9.11
    解析post requests方法返回的response信息
    快手,得物,蓝月亮,蓝禾,奇安信,三七互娱,顺丰,康冠科技,金证科技24春招内推
    cameralink base 接口双通道任意图像数据源模拟
    RFSoC Debug:Petalinux 不显示 flash选项
  • 原文地址:https://blog.csdn.net/weixin_43999327/article/details/126011358