go的derfer
go的derfer

go的derfer

概述

defer一般用于资源的释放和异常的捕捉, 作为Go语言的特性之一.

defer 语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进行最后的return之后(当前函数执行完毕后)再执行.

在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

1.资源的释放

一般我们写读取文件的代码如下:

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 
    }
    dst.Close()
    src.Close()
    return
}

在程序最开始,os.Open及os.Create打开了两个文件资源描述符,并在最后通过file.Close方法得到释放,在正常情况下,该程序能正常运行,一旦在dstName文件创建过程中出现错误,程序就直接返回,src资源将得不到释放。因此需要在所有错误退出时释放资源,即修改为如下代码才能保证其在异常情况下的正确性。


即在每个err里面如果发生了异常, 要及时关闭src的资源.
这个问题出现在加锁中也非常常见

l.lock()

// 如果下面发生了异常
// 我们需要在每个err处理块中都加入l.unlock()来解锁
// 不然资源就得不到释放, 就会产生死锁
if err != nil {
    l.unlock()
    return
}

但是这样做未免太麻烦了, defer优雅的帮我们解决了这个问题
比如我们可以这样

src, err := os.Open(srcName)
defer src.Close()
if err != nil {
    return
}
dst, err := os.Create(dstName)
defer dst.Close()
if err != nil {
    return 
}
------------------------------------------
l.lock()
defer l.unlock()
......
if err != nil {
    return 
}
......

这样写的话, 就不需要在每个异常处理块中都加上Close() 或者 unlock()语句了.

2. 异常的捕捉

程序在运行时可能在任意的地方发生panic异常,例如算术除0错误、内存无效访问、数组越界等,这些错误会导致程序异常退出。在很多时候,我们希望能够捕获这样的错误,同时希望程序能够继续正常执行。一些语言采用try…catch语法,当try块中发生异常时,可以通过catch块捕获。

Go语言使用了特别的方式处理这一问题。defer的特性是无论后续函数的执行路径如何以及是否发生了panic,在函数结束后一定会得到执行,这为异常捕获提供了很好的时机。异常捕获通常结合recover函数一起使用。

package main

import "fmt"
func executePanic(){
    defer fmt.Println("defer func")
    panic("This is Pancic Situation")
    fmt.Println("The function executes Completely")
}

func main(){
    executePanic()
    fmt.Println("Main block is executed completely...")
}

如上所示,在executePanic函数中,手动执行panic函数触发了异常。当异常触发后,函数仍然会调用defer中的函数,然后异常退出。输出如下,表明调用了defer中的函数,并且main函数将不能正常运行,程序异常退出打印出栈追踪信息。

image-20221119133945279

如下所示,当在defer函数中使用recover进行异常捕获后,程序将不会异常退出,并且能够执行正常的函数流程。如下输出表明,尽管有panic,main函数仍然在正常执行后退出

package main

import "fmt"

func executePanic(){
    defer func(){
        if err := recover(); err != nil{
            fmt.Println(err)
        }
    }()
    panic("This is Pancic Situation")
    fmt.Println("The function executes Completely")
}

func main(){
    executePanic()
    fmt.Println("Main block is executed completely...")
}

使用了recover函数后, 程序将不会异常退出, 仍会正常执行

image-20221119140118358

3.多个defer语句的执行顺序

当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出), 相当于开辟了一个延时调用栈

func main() {
    fmt.Println("defer begin")
    // 将defer放入延迟调用栈
    defer fmt.Println(1)
    defer fmt.Println(2)
    // 最后一个放入, 位于栈顶, 最先调用
    defer fmt.Println(3)
    fmt.Println("defer end")
}

执行的结果就是

image-20221119140318219

4.defer执行时的拷贝

package main

import "fmt"

func main(){
    test := func(){
        fmt.Println("test1")
    }
    defer test()
    test = func(){
        fmt.Println("test2")
    }
    fmt.Println("test3")
}

运行结果:

image-20221119140938627


package main

import "fmt"

func main(){
    x := 10
    defer func(a int){
        fmt.Println(a)
    }(x)
    x++
}

函数和变量的值都被压入栈中

运行结果:

image-20221119141204252

换一种闭包函数的方式:

 package main

 import "fmt"

 func main(){
     x := 10
     defer func(){
         fmt.Println(x)
     }()
     x++
 }

结果却是11.

此处的defer函数并没有参数,内部使用的变量的值是全局的值,没有被压栈

5.指针相关

package main

import "fmt"

func main(){
    x := 10
    defer func(a *int){
        fmt.Println(*a)
    }(&x)
    x++
}

image-20221119141530896

为什么运行结果是11呢?

x++,指针指向改变了,原本defer压栈的值是指针,指针没有发生变化,但是指针的指向的值变了,这是defer后面的函数所不知的

6.return和defer的执行顺序

package main

import "fmt"

func f1() int {
    x := 10
    defer func(){
        x++
    }()
    return x
}
func main(){
    fmt.Println(f1())
}

运行结果:

image-20221119142706711

其实这个也是值传递

关于引用传递:

package main

import "fmt"

func f1() *int {
    a := 10
    b := &a
    defer func(){
        *b++
    }()
    return b
}
func main(){
    fmt.Println(*f1())
}

image-20221119145114139

总结

defer本质上市注册了一个延迟函数,defer函数的执行顺序已经确定了

defer没有嵌套,defer的机制是要取代try,excpt,finally

发表回复

您的电子邮箱地址不会被公开。