图解defer

0 阅读4分钟

1.defer的执行顺序:

多个defer出现的时候.它们是一个"栈"的关系.也就是先进后出.在一个函数中.写在前面的defer会比写在后面的defer调用的晚.

示例:

func main() {
    defer func1()
    defer func2()
    defer func3()
}

func func1() {
    fmt.Println("A")
}

func func2() {
    fmt.Println("B")
}

func func3() {
    fmt.Println("C")
}

每次执行到defer语句.并不会立刻执行.而是将defer后的表达式压入栈.如图所示.

所以func1() func2() func3() 会被一次压入栈中.在入栈的过程中程序不会执行defer后面的表达式.当mian函数执行完之后.被压入栈中的defer表达式会依次出栈并且执行.顺序是弹出一个.执行一次.然后弹出下一个.如图所示.

流程图:

2.defer与return谁先谁后:

func main() {
    returnAndDefer()
}

func deferFunc() int {
    fmt.Println("defer func called")
    return 0
}

func returnFunc() int {
    fmt.Println("return func called")
    return 0
}

func returnAndDefer() int {
    defer deferFunc()
    return returnFunc()
}

执行结果:

执行流程:

结论是return语句先执行.defer后面的语句后执行.

defer触发的出栈时机是当前函数的作用域结束.而return语句作为当前函数的最后一条语句显然是在函数结束之前需要执行完的语句.所以在return语句完成动作之前不会触发defer出栈且执行defer之后的表达式语句.

3.函数返回值的初始化:

该点不属于defer本身.但是调用场景却与defer有联系.如func DeferFunc1(i int){}中的返回值t int.这个t会在函数起始处被初始化为对应类型的零值并且作用域为整个函数.如图所示.

DeferFunc1的函数原型有一个返回值t.当执行DeferFunc1(10)的时候.t也会被初始化为0.同时t为0的作用域在DeferFunc1的整个生命周期.直到最后return 2语句将t赋值为2.

示例:

func main() {
     DeferFunc1(10)
}

func DeferFunc1(i int) (t int) {
    fmt.Println("t =", t)
    return 2
}

执行结果:

执行流程:

证明只要声明函数的返回值变量的名称.就会在函数初始化为之赋值为0.而且在函数体作用域可见.

4.有名函数返回值遇见defer的情况:

示例:

func main() {
    fmt.Println(returnButDefer())
}

func returnButDefer() (t int) {
    defer func() {
       t = t * 10
    }()
    return 1
}

执行结果:

执行流程:

在没有defer的情况下.其实函数的返回与return一致.但是有了defer就不一样了.先return在进行defer.

5.defer遇见panic:

能够触发defer的情况是遇见return(或函数体到末尾)和遇见panic.针对defer遇见return.如图所示.

当遇到panic时.会遍历本协程的defer链表.并执行defer.在执行defer的过程中.如果遇到recover.则停止panic.返回recover处继续往下执行.如果没有遇到recover.则遍历完本协程的defer链表后.向stderr抛出panic信息.如图所示.

6.defer遇见panic.但是并不捕获异常的情况:

示例:

func main() {
    defer_call()
}

func defer_call(){
    defer func() {
       fmt.Println("defer:panic之前1")
    }()

    defer func() {
       fmt.Println("defer:panic之前2")
    }()
    
    panic("异常内容")
    
    defer func() {
       fmt.Println("defer:panic之后.永远执行不到")
    }()
}

执行结果:

执行流程:

所以在panic之后的defer无法被触发.因为执行语句并没有将最后一个defer压栈.

7.defer遇见panic.并捕获异常:

示例:

func main() {
    defer_call()
}

func defer_call() {
    defer func() {
       fmt.Println("defer:panic之前1.捕获异常")
       if err := recover(); err != nil {
          fmt.Println(err)
       }
    }()

    defer func() {
       fmt.Println("defer:panic之前2.不捕获")
    }()

    panic("异常内容")

    defer func() {
       fmt.Println("defer:panic之后.永远执行不到")
    }()
}

执行结果:

执行流程:

defer最大的功能是panic后依然有效.所以defer可以保证一些资源一定会被关闭.从而避免一些异常出现的问题.

8.defer中包含panic:

示例:

func main() {
    defer func() {
       if err := recover(); err != nil {
          fmt.Println(err)
       }else {
          fmt.Println("fatal")
       }
    }()
    
    defer func() {
       panic("defer panic")
    }()
    
    panic("panic")
}

执行流程:

panic仅有最后一个可以被recover捕获.触发panic("panic")后defer按顺序出栈执行.第一个被执行的defer中会有panic("defer panic")异常语句.这个异常会覆盖mian中的异常.最后这个异常被第二个执行的defer捕获.

9.defer下的函数参数包含子函数:

示例:

func main() {
    defer function(1, function(3, 0))
    defer function(2, function(4, 0))
}

func function(index int, value int) int {
    fmt.Println(index)
    return index
}

执行结果:

执行流程:

1).defer压栈function1.压栈函数地址 形参1 形参2(调用function3).打印3.

2).defer压栈function2.压栈函数地址 形参1 形参2(调用function4).打印4.

3).defer出栈function2.调用function.打印2.

4).defer出栈function1.调用function1.打印1.

天色沉靛蓝.

语雀地址www.yuque.com/itbosunmian…?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路