Skip to content

Go延迟调用

defergo中一种延迟调用机制,defer后面的函数只有在当前函数执行完毕后才能执行,将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源。

go
defer function([parameter_list]) // 延迟执行函数
defer method([parameter_list]) // 延迟执行方法

defer语句的执行顺序

多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO),写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下:

go
func func1(){
    fmt.Println("我是 func1")
}
func func2(){
    fmt.Println("我是 func2")
}
func func3(){
    fmt.Println("我是 func3")
}
func main(){
    defer func1()
    defer func2()
    defer func3()
    fmt.Println("main1")
    fmt.Println("main2")
}

执行输出如下:

main1

main2

我是 func3

我是 func2

我是 func1

延迟函数的参数

defer后面的函数在入栈的时候保存的是入栈那一刻的值,会将变量的实际值传入栈中,而不会在后期根据变量改变而改变。

go
func main(){
   i:= 0
   defer func(a int) {
		fmt.Println(a)
	}(i)
    i++
}

此时输出的值是0,而不是1,因为defer后面的函数在入栈的时候保存的是入栈那一刻的值,而当时i的值是0,所以后期对i进行修改,并不会影响栈内函数的值。

如果我们把参数传引用

go
func main(){
   i:= 0
   defer func(a *int) {
		fmt.Println(*a)
	}(&i)
    i++
}

此时输出的值是1,因为这里defer后面函数入栈的时候唇乳的执行变量i的指针,后期i值改变的时候,输出结果也会改变。

延迟函数进阶

go
package main

import "fmt"

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(t.name, " closed")
}
func main() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        defer t.Close()
    }
}

这是一个初学者常犯的错误.

错误结果1:

a closed
b closed
c closed

没有考虑defer的LIFO特性,简单的认为和return类似导致的错误.

错误结果2:

c  closed
b  closed
a  closed

考虑了defer的特性.认为在循环调用时,每次会在循环的时候调用defer语句,最终输出c b a.

正确结果

c closed
c closed
c closed

在运行中,栈中存入了三个defer t.close()语句,而t在结束的时候是指向同一个变量t,其值为c,所以输出了ccc.

go
package main

import "fmt"

func foo() (i int) {

    i = 0
    defer func() {
        fmt.Println(i)
    }()

    return 2
}

func main() {
    foo()
}
  1. 函数声明了一个命名返回值i
  2. 函数内部有一个defer语句,它延迟执行一个匿名函数,这个匿名函数打印出i的值。
  3. 函数通过return 2结束,这实际上将i设置为2
  4. 在函数返回之前,defer语句执行。此时,匿名函数中打印的是命名返回值i的当前值,也就是2