Go语言defer的延迟执行机制

发布时间 2024-01-12 17:52:36作者: 易先讯

1        题目(单选题)

如下Go语言程序的输出结果是()

package main

import "fmt"

func f1(name string) string {

    fmt.Println("in f1", name)

    return name

}

func f2(name string) string {

    fmt.Println("in f2", name)

    return name

}

func f3() {

    defer f2(f1("function"))

    fmt.Println("in f3 function")

}

func main() {

    f3()

}

  1. in f1 function
    in f3 function
    in f2 function
  2. in f1 function
    in f2 function
    in f3 function
  3. in f3 function
    in f1 function
    in f2 function
  4. in f3 function
    in f2 function
    in f1 function

2        实际答题情况

正确答案 A。

各个选项的选择率为:

A

20.2%

B

0.8%

C

78.2%

D

0.8%

3        知识点解读

defer 是 Go 语言中用于延迟执行函数调用的关键字,defer 语句主要包括下面这些知识点:

1)   延迟执行

defer语句会将紧跟其后的函数调用推迟到包含该defer语句的函数返回前执行。

这个特性使得程序员可以在不改变函数逻辑的情况下,确保在函数结束时执行特定操作。

例如:

package main

 

import "fmt"

 

func main() {

    defer cleanUp()

    fmt.Println("hello")

}

func cleanUp() {

    fmt.Println("world")

}

运行结果为:

hello

world

2)   执行顺序

如果在一个函数中有多个defer语句,会按照后进先出(LIFO,Last In First Out)的原则执行,即最后声明的defer函数最先被执行,以此类推直至所有的defer函数都被执行完毕。举个例子:

package main

 

import "fmt"

 

func add(a, b int) {

    fmt.Println("Result =", a + b)

}

 

func main() {

    fmt.Println("Begin")

    defer fmt.Println("End")

    defer add(36, 64)

    defer add(2, 8)

}

运行结果:

Begin

Result = 10

Result = 100

End

3)   与return配合使用

当 defer 与 return 同时存在时,先执行return语句,再执行defer语句。

下面例子演示了defer 与 return 执行的先后顺序:

package main

 

import "fmt"

 

func deferFunc() {

    fmt.Println("defer func called")

}

 

func returnFunc() int {

    fmt.Println("return func called")

    return 0

}

 

func returnAndDefer() int {

    defer deferFunc()

    return returnFunc()

}

 

func main() {

    returnAndDefer()

}

输出结果:

return func called

defer func called

可以看到,return 后面的语句先执行,defer 后面的语句后执行

4)   函数参数的值

在 defer 声明时,函数的参数会立即求值并被保存,但实际调用会延迟到包含 defer 语句的函数即将返回时执行。

package main

 

import "fmt"

 

func printIndex(index int, _ int) int {

    fmt.Print(index)

    return index

}

 

func main() {

    defer printIndex(1, printIndex(3, 0))

    defer printIndex(2, printIndex(4, 0))

}

这里有 4 次函数调用,index 分别为 1,2,3,4。

那么这 4 次函数的先后执行顺序是什么呢?这里面有两个 defer 语句, 所以 defer 一共会压栈两次:先压 printIndex(1, printIndex(3, 0)),后压 printIndex(2, printIndex(4, 0))。

在压栈 printIndex(1, printIndex(3, 0)) 的时候,需要连同函数地址、函数形参一同进栈,为了得到 printIndex(1, printIndex(3, 0)) 的第二个参数的结果,所以需要先执行printIndex(3, 0)将第二个参数算出,于是 printIndex(3, 0) 就被第一个执行。

同理压栈 printIndex(2, printIndex(4, 0)),需要执行 printIndex(4, 0),于是 printIndex(4, 0) 就被第二个执行。

执行顺序如下:

(1)    defer 压栈 printIndex(1, printIndex(3, 0)),压栈函数地址、形参 1、形参 2 (调用 printIndex(3, 0)) –> 打印 3

(2)    defer 压栈 printIndex(2, printIndex(4, 0)),压栈函数地址、形参 1、形参 2 (调用 printIndex(4, 0)) –> 打印 4

(3)    defer 出栈 printIndex(2, printIndex(4, 0)), 调用 printIndex2 –> 打印 2

(4)    defer 出栈 printIndex(1, printIndex(3, 0)), 调用 printIndex1 –> 打印 1

最终输出结果:3421

5)   匿名函数

defer 延迟执行时,如果函数调用中使用了匿名函数或闭包,它们可能会对外部变量产生影响,因为它们保留了对外部变量的引用,例如:

package main

 

import "fmt"

 

func returnButDefer() (t int) { // 命名返回值t初始化0, 并且作用域为该函数全域

    defer func() {

        t = t * 10

    }()

 

    return 1

}

 

func main() {

    fmt.Println(returnButDefer())

}

returnButDefer()原本应该返回1,但是在 return 之后,又被 defer 的匿名 func 函数执行,所以 t = t * 10 被执行,最后 returnButDefer() 返回给上层 main() 的结果为 10

输出结果:10

4        题目解析

我们来分析题目中f3函数中defer语句的执行过程:

(1)当f3函数执行到defer语句时,会先执行f1("function")这个函数调用;所以,"in f1 function"会首先被打印出来。

这是因为“在 defer 声明时,函数的参数会立即求值并被保存”。

(2)然后,f1函数会返回name这个字符串(内容为"function")。

(3)接着,defer语句会将f2(f1("function"))这个函数调用延迟到f3函数执行完毕之后再执行。

(4)最后,f3函数会打印出"in f3 function"。然后,defer语句中延迟的函数调用f2(f1("function"))会被执行。最后"in f2 function"会被打印出来。

因此,这段程序的输出结果是:

A.

in f1 function

in f3 function

in f2 function

这个题目涉及到了Go语言中defer语句的执行时机以及函数调用的顺序;通过这个案例,我们可以更好地理解了defer语句的作用和函数调用的执行顺序。

5        总结

defer 是在 Go 语言中确保清理工作执行的一种机制,常用于资源释放,如文件关闭、锁的释放、数据库连接的关闭等。它保证这些清理工作在函数执行结束后执行,有效地预防了因忘记释放资源而导致的问题。

这个特性使得 defer 在资源管理非常有用,通过延迟执行,它确保了资源的及时释放。然而,过度使用 defer 可能增加代码的复杂性和内存消耗,因此需要谨慎使用,避免滥用。

6        推荐学习资料

Go语言官方文档:

https://go.dev/blog/defer-panic-and-recover

https://medium.com/codex/what-do-you-need-to-know-about-golangs-defer-4fac71e0f00b