go语言之defer延迟调用 | 青训营笔记

103 阅读4分钟

这是我参与「第三届青训营-后端场」笔记创作活动的的第6篇笔记. 在前几日的学习中,发现代码中常有看到 defer 延迟调用,这在以前学习的编程语言c、c++、java、python中都不曾接触到,所以本文就defer延迟调用做一总结: 在了解defer之前,我们先来说一说匿名函数和闭包,因为在go语言中会把defer与匿名函数结合使用。

一、匿名函数与闭包

所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。

在Go语言里,所有的匿名函数(Go语言规范中称之为函数字面量)都是闭包。匿名函数是指不需要定义函数名的一种函数实现方式,它并不是一个新概念,最早可以回溯到1958年的Lisp语言。

func main() {
    i := 0
    str := "mike"
 
    //方式1
    f1 := func() { //匿名函数,无参无返回值
        //引用到函数外的变量
        fmt.Printf("方式1:i = %d, str = %s\n", i, str)
    }
 
    f1() //函数调用
 
    //方式1的另一种方式
    type FuncType func() //声明函数类型, 无参无返回值
    var f2 FuncType = f1
    f2() //函数调用
 
    //方式2
    var f3 FuncType = func() {
        fmt.Printf("方式2:i = %d, str = %s\n", i, str)
    }
    f3() //函数调用
 
    //方式3
    func() { //匿名函数,无参无返回值
        fmt.Printf("方式3:i = %d, str = %s\n", i, str)
    }() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
 
    //方式4, 匿名函数,有参有返回值
    v := func(a, b int) (result int) {
        result = a + b
        return
    }(1, 1) //别忘了后面的(1, 1), (1, 1)的作用是,此处直接调用此匿名函数, 并传参
    fmt.Println("v = ", v)
 
}

闭包捕获外部变量特点:

func main() {
    i := 10
    str := "mike"
    func() {
        i = 100
        str = "go"
        //内部:i = 100, str = go
        fmt.Printf("内部:i = %d, str = %s\n", i, str)
    }() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
 
    //外部:i = 100, str = go
    fmt.Printf("外部:i = %d, str = %s\n", i, str)
}

函数返回值为匿名函数:

// squares返回一个匿名函数,func() int
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
    var x int
    return func() int {//匿名函数
        x++ //捕获外部变量
        return x * x
    }
}
 
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}

函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。

二、延迟调用defer

1 defer作用

关键字 defer ⽤于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。注意,defer语句只能出现在函数或方法的内部。

func main() {
    fmt.Println("this is a test")
    defer fmt.Println("this is a defer") //main结束前调用
 
    /*
        运行结果:
        this is a test
        this is a defer
    */
}
 
​
 

2 多个defer执行顺序

如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执⾏。

 
func test(x int) {
    fmt.Println(100 / x)//x为0时,产生异常
}
 
func main() {
    defer fmt.Println("aaaaaaaa")
    defer fmt.Println("bbbbbbbb")
 
    defer test(0)
 
    defer fmt.Println("cccccccc")
    /*
    运行结果:
    cccccccc
    bbbbbbbb
    aaaaaaaa
    panic: runtime error: integer divide by zero
    */
}
 

3 defer和匿名函数结合使用

func main() {
    a, b := 10, 20
    defer func(x int) { // a以值传递方式传给x
        fmt.Println("defer:", x, b) // b 闭包引用
    }(a)
 
    a += 10
    b += 100
 
    fmt.Printf("a = %d, b = %d\n", a, b)
 
    /*
        运行结果:
        a = 20, b = 120
        defer: 10 120
    */
}

三、总结

这是我参与「第三届青训营-后端场」笔记创作活动的的第5篇笔记。

关于defer总结如下:defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。