欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[系列] Go 使用 defer 函数 要注意的几个点

程序员文章站 2022-05-07 09:28:28
概述 defer 函数大家肯定都用过,它在声明时不会立刻去执行,而是在函数 return 后去执行的。 它的主要应用场景有异常处理、记录日志、清理数据、释放资源 等等。 这篇文章不是分享 defer 的应用场景,而是分享使用 defer 需要注意的点。 咱们先从一道题开始,一起来感受下 ... 输出 ......

概述

defer 函数大家肯定都用过,它在声明时不会立刻去执行,而是在函数 return 后去执行的。

它的主要应用场景有异常处理、记录日志、清理数据、释放资源 等等。

这篇文章不是分享 defer 的应用场景,而是分享使用 defer 需要注意的点。

咱们先从一道题开始,一起来感受下 ...

func calc(index string, a, b int) int {
    ret := a + b
    fmt.println(index, a, b, ret)
    return ret
}

func main() {
    x := 1
    y := 2
    defer calc("a", x, calc("b", x, y))
    x = 3
    defer calc("c", x, calc("d", x, y))
    y = 4
}

输出什么?

...

[系列] Go 使用 defer 函数 要注意的几个点

接下来,先容我分享几个小例子,再进行作答。

执行顺序

func main() {

    defer fmt.println("1")
    defer fmt.println("2")
    defer fmt.println("3")

    fmt.println("main")
}

输出:

main
3
2
1

结论:defer 函数定义的顺序 与 实际执的行顺序是相反的,也就是最先声明的最后才执行。

闭包

func main() {

    var a = 1
    var b = 2

    defer fmt.println(a + b)

    a = 2

    fmt.println("main")
}

输出:

main
3

稍微修改一下,再看看:

func main() {
    var a = 1
    var b = 2

    defer func() {
        fmt.println(a + b)
    }()

    a = 2

    fmt.println("main")
}

输出:

main
4

结论:闭包获取变量相当于引用传递,而非值传递。

稍微再修改一下,再看看:

func main() {
    var a = 1
    var b = 2

    defer func(a int, b int) {
        fmt.println(a + b)
    }(a, b)

    a = 2

    fmt.println("main")
}

输出:

main
3

结论:传参是值复制。

还可以理解为:defer 调用的函数,参数的值在 defer 定义时就确定了,看下代码

defer fmt.println(a + b),在这时,参数的值已经确定了。

而 defer 函数内部所使用的变量的值需要在这个函数运行时才确定,看下代码

defer func() { fmt.println(a + b) }(),a 和 b 的值在函数运行时,才能确定。

return

func t1() int {
    a := 1
    defer func() {
        a++
    }()
    return a
}

输出:1

func t2() (a int) {
    defer func() {
        a++
    }()
    return 1
}

输出:2

func t3() (b int) {
    a := 1
    defer func() {
        a++
    }()
    return 1
}

输出:1

func t4() (a int) {
    defer func(a int) {
        a++
    }(a)
    return 1
}

输出:1

结论:return 不是原子操作。

os.exit

func main() {
    defer fmt.println("1")
    fmt.println("main")
    os.exit(0)
}

输出:main

结论:当os.exit()方法退出程序时,defer不会被执行。

不同协程

func main() {
    goa()
    time.sleep(1 * time.second)
    fmt.println("main")
}

func goa() {
    defer (func(){
        if err := recover(); err != nil {
            fmt.println("panic:" + fmt.sprintf("%s", err))
        }
    })()

    go gob()
}

func gob() {
    panic("error")
}

gob() panic 捕获不到。

结论:defer 只对当前协程有效。

这个问题怎么解?咱们下回再说。

接下来,咱们分析下文章开头的问题吧。

答案解析

先列出答案:

b 1 2 3
d 3 2 5
c 3 5 8
a 1 3 4

其实上面那道题,可以拆解为:

func calc(index string, a, b int) int {
    ret := a + b
    fmt.println(index, a, b, ret)
    return ret
}

func main() {
    x := 1
    y := 2
    tmp1 := calc("b", x, y)
    defer calc("a", x, tmp1)
    x = 3
    tmp2 := calc("d", x, y)
    defer calc("c", x, tmp2)
    y = 4
}

所以顺序就是:b d c a。

执行到 tmp1 时,输出:b 1 2 3。

执行到 tmp2 时,输出:d 3 2 5。

根据 defer 执行顺序原则,先声明的后执行,所以下一个该执行 c 了。

又因为传参是值赋值,所以在 a 的时候,无法用到 x = 3y = 4,在 c 的时候,无法用到 y = 4

执行到 c 时,输出:c 3 5 8

执行到 a 时,输出:a 1 3 4

到这,基本上 defer 就清楚了,大家可以根据自己的理解去记忆。

go-gin-api 系列文章